mirror of
https://github.com/LOOHP/ImageFrame.git
synced 2026-11-05 09:10:19 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
946840c14a | ||
|
|
46a17d9caa | ||
|
|
a97768028d | ||
|
|
bc0a073813 | ||
|
|
a7369c5fe7 | ||
|
|
7edeaa29fd | ||
|
|
c03d6c44a7 | ||
|
|
a41bfa7fa8 | ||
|
|
d5c20ec243 | ||
|
|
bbcdff0373 | ||
|
|
89da42a483 | ||
|
|
a33c6f4b1f | ||
|
|
fed3b4c3c8 | ||
|
|
c7620ce1fe | ||
|
|
2e2146f1fc | ||
|
|
ff81317be1 | ||
|
|
2d7e08b79f | ||
|
|
3527a881e4 | ||
|
|
1cdb73099e | ||
|
|
aa1a403b95 | ||
|
|
809ea6381b | ||
|
|
f9f88e16ce | ||
|
|
b766d2f9d5 | ||
|
|
cc50d7c2c9 | ||
|
|
36d452cdfe | ||
|
|
8cc3219a33 | ||
|
|
98b2b1982e | ||
|
|
fc1e14583c | ||
|
|
25eedbe0be | ||
|
|
33e7fdc7c2 | ||
|
|
1157ca1813 | ||
|
|
5a3894d6fe | ||
|
|
61e1f5aab3 | ||
|
|
05948afdba | ||
|
|
116e94c2f7 | ||
|
|
34366439bb | ||
|
|
00245cd8d1 | ||
|
|
aeedecd808 | ||
|
|
48096bbd78 | ||
|
|
105b4f41a1 | ||
|
|
861efce918 | ||
|
|
fa617744b8 | ||
|
|
2bd23ae498 | ||
|
|
5879d44242 | ||
|
|
cd430775f3 | ||
|
|
d035989df5 | ||
|
|
6c1ddfcf05 | ||
|
|
33543b9edb | ||
|
|
5379ef308e | ||
|
|
5ade6c9b24 | ||
|
|
c339a51723 | ||
|
|
3130ddf5cc | ||
|
|
2a7fe930aa | ||
|
|
d706da2f4b | ||
|
|
184b9749ab | ||
|
|
59f0ee33f5 | ||
|
|
a5a309e2d4 | ||
|
|
93c1911d60 | ||
|
|
e25eff4fac | ||
|
|
837a9a5b73 | ||
|
|
07ecafa7da | ||
|
|
fd7b071b5d | ||
|
|
1322dcdb91 | ||
|
|
c197158850 | ||
|
|
98f429d04c | ||
|
|
c6e04888c6 | ||
|
|
0cfb292ac3 | ||
|
|
7677eef07c | ||
|
|
49feef2999 | ||
|
|
0162cebc41 | ||
|
|
0295f9ef5a | ||
|
|
d64cbfccfc | ||
|
|
4a9ca90188 | ||
|
|
d18b0743db | ||
|
|
6d231e52c6 | ||
|
|
3ea1420304 | ||
|
|
2a1adee462 | ||
|
|
e8204c1fa9 | ||
|
|
c37a1fda8f | ||
|
|
a9b41c814c | ||
|
|
430a658e0e | ||
|
|
ff8f5febac | ||
|
|
fe7a551be8 | ||
|
|
2c2d719143 | ||
|
|
3cfdc8f507 | ||
|
|
8a89a33b42 | ||
|
|
6af8fd7b76 | ||
|
|
f412275b37 | ||
|
|
9b03eb6ea4 | ||
|
|
c40f614a61 | ||
|
|
683bbdcde0 | ||
|
|
0c2d8f7787 | ||
|
|
ea0563ff50 | ||
|
|
1b2f47f8db | ||
|
|
cdfc2b480b | ||
|
|
fcfe414ba2 | ||
|
|
e203b3b4d3 | ||
|
|
7e05d59387 | ||
|
|
4106f0bcd5 | ||
|
|
23d2113ca7 | ||
|
|
e1fe6dc81e | ||
|
|
708d94990e | ||
|
|
6120ee8fa8 | ||
|
|
fa3e26823f | ||
|
|
3cb325a200 | ||
|
|
b33c0f1eee | ||
|
|
11daa22cd5 | ||
|
|
42462cc9d2 | ||
|
|
1190040ccc | ||
|
|
8d5353c471 | ||
|
|
91b8e00dbf | ||
|
|
34f9a2c84b | ||
|
|
fa2a5d2f2d | ||
|
|
1c69e26e01 | ||
|
|
4990b53474 | ||
|
|
5212f0522e | ||
|
|
75267c1305 | ||
|
|
b8371a21bc | ||
|
|
085ce5222f | ||
|
|
306d57b0c7 | ||
|
|
9b8efe2295 | ||
|
|
e3cc812425 | ||
|
|
286ad3547e | ||
|
|
39e435f1e4 | ||
|
|
08c22adc1b | ||
|
|
f23b67a64c | ||
|
|
af8597354a | ||
|
|
82c81b77c1 | ||
|
|
b7403f9481 | ||
|
|
dbd0b8a660 | ||
|
|
520735e389 | ||
|
|
fafe4e1bb4 | ||
|
|
7118fcae81 | ||
|
|
2249c7568f | ||
|
|
ec29a575e6 | ||
|
|
a344a1159d | ||
|
|
5e1ccd588e | ||
|
|
7f0ad6a3a6 | ||
|
|
296d3d87fc | ||
|
|
a667937148 | ||
|
|
c25533e16c | ||
|
|
cffe0a02f9 | ||
|
|
c0cf0ea2b7 | ||
|
|
6e23bbebef | ||
|
|
4d3266d92a | ||
|
|
d7f9cf334a | ||
|
|
ee17be5afd | ||
|
|
c8054f72cb | ||
|
|
e27ac14a31 | ||
|
|
d6bb052d12 | ||
|
|
3e482b37b3 | ||
|
|
8f563d3820 | ||
|
|
e66fcde6b8 | ||
|
|
0e2245af2e | ||
|
|
2a25cbff2d | ||
|
|
3eac4d6828 | ||
|
|
2c50d8e382 | ||
|
|
c3308febb5 | ||
|
|
5f6cd8735c | ||
|
|
f077708ad1 | ||
|
|
0fad2018b5 | ||
|
|
b59e718a56 | ||
|
|
425c9bca20 | ||
|
|
b4afa61420 | ||
|
|
e11d136d14 | ||
|
|
54eaaa1e55 | ||
|
|
ecb2bac48b | ||
|
|
e74750c191 | ||
|
|
0cf28de45d | ||
|
|
c6a92dc7ce | ||
|
|
9e97576618 | ||
|
|
4b5b64a9a3 | ||
|
|
7da31597f0 | ||
|
|
9272225f46 | ||
|
|
2e89121489 | ||
|
|
98f32c78d5 | ||
|
|
a1c1f5f555 | ||
|
|
9fe0c2f6dc | ||
|
|
6c7fcf6117 | ||
|
|
8eb8fa865f | ||
|
|
1a38c9b078 | ||
|
|
3eb9401e8a | ||
|
|
42cfa1fd87 | ||
|
|
6e365e8bea | ||
|
|
013135bf9a | ||
|
|
21b5971b24 | ||
|
|
9fc17997b2 | ||
|
|
fa46d3a552 | ||
|
|
ad1520bb85 | ||
|
|
dd5a7e56e8 | ||
|
|
9ba60be63b | ||
|
|
ecb034bb64 | ||
|
|
a74a8d7252 | ||
|
|
8e1da37d1b | ||
|
|
6f1538bc0a | ||
|
|
bb0703d123 | ||
|
|
751b2ce5a2 | ||
|
|
94bb83692a | ||
|
|
7c959ab10f | ||
|
|
2c56682115 | ||
|
|
7f2fd61790 | ||
|
|
a90c557a1a | ||
|
|
13a7ff04a0 | ||
|
|
ae8383a9ef | ||
|
|
a186ccf707 | ||
|
|
ad02c8c006 | ||
|
|
baf6914f6d | ||
|
|
f3505bc28c | ||
|
|
c49fa30d1e | ||
|
|
5b0edb02e2 | ||
|
|
f2f525c37d | ||
|
|
92a698205f | ||
|
|
cdb7a52aac | ||
|
|
68fabd8b10 | ||
|
|
26c8250d53 | ||
|
|
879829c2c0 | ||
|
|
ce69b915a1 | ||
|
|
b5b8e40138 | ||
|
|
370ed67d8f | ||
|
|
adfc4e8f69 | ||
|
|
72ce0a236e | ||
|
|
ac8a977683 | ||
|
|
1ce29654a9 | ||
|
|
3e0fd9be94 | ||
|
|
e69dc2a16d | ||
|
|
a76f14e87b | ||
|
|
8343d79f3c | ||
|
|
7051155fa1 | ||
|
|
2a11c93fc9 | ||
|
|
628ab4123c | ||
|
|
10f0c15e7a | ||
|
|
4127f8b643 |
No files matched your search
+112
@@ -0,0 +1,112 @@
|
||||
# User-specific stuff
|
||||
.idea/
|
||||
|
||||
*.iml
|
||||
*.ipr
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# Compiled class file
|
||||
*.class
|
||||
|
||||
# Log file
|
||||
*.log
|
||||
|
||||
# BlueJ files
|
||||
*.ctxt
|
||||
|
||||
# Package Files #
|
||||
*.war
|
||||
*.nar
|
||||
*.ear
|
||||
*.zip
|
||||
*.tar.gz
|
||||
*.rar
|
||||
|
||||
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||
hs_err_pid*
|
||||
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
target/
|
||||
|
||||
pom.xml.tag
|
||||
pom.xml.releaseBackup
|
||||
pom.xml.versionsBackup
|
||||
pom.xml.next
|
||||
|
||||
release.properties
|
||||
dependency-reduced-pom.xml
|
||||
buildNumber.properties
|
||||
.mvn/timing.properties
|
||||
.mvn/wrapper/maven-wrapper.jar
|
||||
.flattened-pom.xml
|
||||
|
||||
# Common working directory
|
||||
run/
|
||||
@@ -1,7 +1,9 @@
|
||||
[](https://ci.loohpjames.com/job/ImageFrame/)
|
||||
[](https://ci.loohpjames.com/job/ImageFrame/) [](https://crowdin.com/project/imageframe)
|
||||
# ImageFrame
|
||||
|
||||
https://www.spigotmc.org/resources/-1/
|
||||
https://www.spigotmc.org/resources/106031/<br>
|
||||
https://modrinth.com/plugin/imageframe<br>
|
||||
https://hangar.papermc.io/LOOHP/ImageFrame
|
||||
|
||||
Put images on maps and walls!
|
||||
|
||||
@@ -32,6 +34,10 @@ Plugins built against Spigot usually also work with [Paper](https://papermc.io/)
|
||||
```
|
||||
Replace `VERSION` with the version number.
|
||||
|
||||
## Official Addons
|
||||
|
||||
- [ImageFrameClient](https://github.com/LOOHP/ImageFrameClient) (Complementary client mod for servers with ImageFrame to display HD and full color images)
|
||||
|
||||
## Partnerships
|
||||
|
||||
### Server Hosting
|
||||
@@ -42,4 +48,4 @@ It also **supports my development**, take it as an alternative way to donate whi
|
||||
|
||||
**https://www.bisecthosting.com/loohp**
|
||||
|
||||

|
||||
[](https://www.bisecthosting.com/loohp)
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_16</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.16.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.16.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import com.loohp.imageframe.utils.UnsafeAccessor;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.minecraft.server.v1_16_R1.ChunkProviderServer;
|
||||
import net.minecraft.server.v1_16_R1.DataWatcher;
|
||||
import net.minecraft.server.v1_16_R1.DataWatcherObject;
|
||||
import net.minecraft.server.v1_16_R1.EntityHuman;
|
||||
import net.minecraft.server.v1_16_R1.EntityItemFrame;
|
||||
import net.minecraft.server.v1_16_R1.EntityPlayer;
|
||||
import net.minecraft.server.v1_16_R1.IChatBaseComponent;
|
||||
import net.minecraft.server.v1_16_R1.ItemWorldMap;
|
||||
import net.minecraft.server.v1_16_R1.MapIcon;
|
||||
import net.minecraft.server.v1_16_R1.MinecraftKey;
|
||||
import net.minecraft.server.v1_16_R1.NBTTagCompound;
|
||||
import net.minecraft.server.v1_16_R1.NBTTagList;
|
||||
import net.minecraft.server.v1_16_R1.NBTTagString;
|
||||
import net.minecraft.server.v1_16_R1.Packet;
|
||||
import net.minecraft.server.v1_16_R1.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.server.v1_16_R1.PacketPlayOutMap;
|
||||
import net.minecraft.server.v1_16_R1.PersistentIdCounts;
|
||||
import net.minecraft.server.v1_16_R1.PlayerChunkMap;
|
||||
import net.minecraft.server.v1_16_R1.PlayerInventory;
|
||||
import net.minecraft.server.v1_16_R1.ResourceKey;
|
||||
import net.minecraft.server.v1_16_R1.WorldMap;
|
||||
import net.minecraft.server.v1_16_R1.WorldServer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import org.bukkit.craftbukkit.v1_16_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_16_R1.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_16_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_16_R1.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_16_R1.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_16_R1.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_16 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field nmsMapIconTypeDisplayField;
|
||||
private final Field[] nmsPacketPlayOutEntityMetadataFields;
|
||||
private final Field nmsItemFrameItemStackDataWatcherField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_16() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.server.v1_16_R1.Entity.class.getDeclaredField("T");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
nmsMapIconTypeDisplayField = MapIcon.Type.class.getDeclaredField("C");
|
||||
nmsPacketPlayOutEntityMetadataFields = PacketPlayOutEntityMetadata.class.getDeclaredFields();
|
||||
nmsItemFrameItemStackDataWatcherField = EntityItemFrame.class.getDeclaredField("ITEM");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("a");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.colors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.humans;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.humans;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
try {
|
||||
nmsMapIconTypeDisplayField.setAccessible(true);
|
||||
return nmsMapIconTypeDisplayField.getBoolean(mapIconType);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "resource"})
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.getMinecraftServer().D().getWorldPersistentData().a(PersistentIdCounts::new, "idcounts");
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked", "resource"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.server.v1_16_R1.World> worldTypeKey = worldServer.getDimensionKey();
|
||||
String mapId = ItemWorldMap.a(id);
|
||||
WorldMap worldMap = new WorldMap(mapId);
|
||||
worldMap.a(spawnLocation.getBlockX(), spawnLocation.getBlockZ(), 3, false, false, worldTypeKey);
|
||||
worldServer.a(worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.getMinecraftServer().D().getWorldPersistentData().a(PersistentIdCounts::new, "idcounts");
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.getChunkProvider();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.playerChunkMap;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.trackedEntities;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (EntityPlayer player : entityTracker.trackedPlayers) {
|
||||
players.add(player.getBukkitEntity());
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? Collections.emptyList() : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
byte[] colors0;
|
||||
int size;
|
||||
if (colors == null) {
|
||||
colors0 = EMPTY_BYTE_ARRAY;
|
||||
size = 0;
|
||||
} else {
|
||||
colors0 = colors;
|
||||
size = 128;
|
||||
}
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, false, mapIcons, colors0, 0, 0, size, size);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameItemStackDataWatcherField.setAccessible(true);
|
||||
DataWatcherObject<net.minecraft.server.v1_16_R1.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.server.v1_16_R1.ItemStack>) nmsItemFrameItemStackDataWatcherField.get(null);
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(dataWatcherObject, CraftItemStack.asNMSCopy(itemStack)));
|
||||
PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata();
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.server.v1_16_R1.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.getDataWatcher();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(byteField, value));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().playerConnection.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.server.v1_16_R1.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getTag();
|
||||
if (tag == null || !tag.hasKey(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getInt(CombinedMapItemInfo.KEY);
|
||||
if (!tag.hasKey(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.hasKey(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.server.v1_16_R1.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getOrCreateTag();
|
||||
tag.setInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.setFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.setIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.server.v1_16_R1.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getTag();
|
||||
if (tag == null || !tag.hasKey(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getInt(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.getInt(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.server.v1_16_R1.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getOrCreateTag();
|
||||
tag.setInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.setInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.server.v1_16_R1.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == 9) {
|
||||
NBTTagList loreLineTagList = displayTag.getList("Lore", 8);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.getString(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
TranslatableComponent translatableComponent = new TranslatableComponent("effect.minecraft.invisibility");
|
||||
translatableComponent.setColor(ChatColor.GRAY);
|
||||
translatableComponent.setItalic(false);
|
||||
loreLines.add(0, ComponentSerializer.toString(translatableComponent));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.set("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.inventory;
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.server.v1_16_R1.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.pickup(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.activeContainer.c();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
WorldServer nmsWorld = ((CraftWorld) world).getHandle();
|
||||
MinecraftKey key = nmsWorld.getTypeKey().a();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_16_2</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.16.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.16.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import com.loohp.imageframe.utils.UnsafeAccessor;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.minecraft.server.v1_16_R2.ChunkProviderServer;
|
||||
import net.minecraft.server.v1_16_R2.DataWatcher;
|
||||
import net.minecraft.server.v1_16_R2.DataWatcherObject;
|
||||
import net.minecraft.server.v1_16_R2.EntityHuman;
|
||||
import net.minecraft.server.v1_16_R2.EntityItemFrame;
|
||||
import net.minecraft.server.v1_16_R2.EntityPlayer;
|
||||
import net.minecraft.server.v1_16_R2.IChatBaseComponent;
|
||||
import net.minecraft.server.v1_16_R2.ItemWorldMap;
|
||||
import net.minecraft.server.v1_16_R2.MapIcon;
|
||||
import net.minecraft.server.v1_16_R2.MinecraftKey;
|
||||
import net.minecraft.server.v1_16_R2.NBTTagCompound;
|
||||
import net.minecraft.server.v1_16_R2.NBTTagList;
|
||||
import net.minecraft.server.v1_16_R2.NBTTagString;
|
||||
import net.minecraft.server.v1_16_R2.Packet;
|
||||
import net.minecraft.server.v1_16_R2.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.server.v1_16_R2.PacketPlayOutMap;
|
||||
import net.minecraft.server.v1_16_R2.PersistentIdCounts;
|
||||
import net.minecraft.server.v1_16_R2.PlayerChunkMap;
|
||||
import net.minecraft.server.v1_16_R2.PlayerInventory;
|
||||
import net.minecraft.server.v1_16_R2.ResourceKey;
|
||||
import net.minecraft.server.v1_16_R2.WorldMap;
|
||||
import net.minecraft.server.v1_16_R2.WorldServer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import org.bukkit.craftbukkit.v1_16_R2.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_16_R2.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_16_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_16_R2.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_16_R2.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_16_R2.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_16_2 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field nmsMapIconTypeDisplayField;
|
||||
private final Field[] nmsPacketPlayOutEntityMetadataFields;
|
||||
private final Field nmsItemFrameItemStackDataWatcherField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_16_2() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.server.v1_16_R2.Entity.class.getDeclaredField("S");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
nmsMapIconTypeDisplayField = MapIcon.Type.class.getDeclaredField("C");
|
||||
nmsPacketPlayOutEntityMetadataFields = PacketPlayOutEntityMetadata.class.getDeclaredFields();
|
||||
nmsItemFrameItemStackDataWatcherField = EntityItemFrame.class.getDeclaredField("ITEM");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("a");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.colors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.humans;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.humans;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
try {
|
||||
nmsMapIconTypeDisplayField.setAccessible(true);
|
||||
return nmsMapIconTypeDisplayField.getBoolean(mapIconType);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "resource"})
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.getMinecraftServer().E().getWorldPersistentData().a(PersistentIdCounts::new, "idcounts");
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked", "resource"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.server.v1_16_R2.World> worldTypeKey = worldServer.getDimensionKey();
|
||||
String mapId = ItemWorldMap.a(id);
|
||||
WorldMap worldMap = new WorldMap(mapId);
|
||||
worldMap.a(spawnLocation.getBlockX(), spawnLocation.getBlockZ(), 3, false, false, worldTypeKey);
|
||||
worldServer.a(worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.getMinecraftServer().E().getWorldPersistentData().a(PersistentIdCounts::new, "idcounts");
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.getChunkProvider();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.playerChunkMap;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.trackedEntities;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (EntityPlayer player : entityTracker.trackedPlayers) {
|
||||
players.add(player.getBukkitEntity());
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? Collections.emptyList() : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
byte[] colors0;
|
||||
int size;
|
||||
if (colors == null) {
|
||||
colors0 = EMPTY_BYTE_ARRAY;
|
||||
size = 0;
|
||||
} else {
|
||||
colors0 = colors;
|
||||
size = 128;
|
||||
}
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, false, mapIcons, colors0, 0, 0, size, size);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameItemStackDataWatcherField.setAccessible(true);
|
||||
DataWatcherObject<net.minecraft.server.v1_16_R2.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.server.v1_16_R2.ItemStack>) nmsItemFrameItemStackDataWatcherField.get(null);
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(dataWatcherObject, CraftItemStack.asNMSCopy(itemStack)));
|
||||
PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata();
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.server.v1_16_R2.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.getDataWatcher();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(byteField, value));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().playerConnection.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.server.v1_16_R2.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getTag();
|
||||
if (tag == null || !tag.hasKey(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getInt(CombinedMapItemInfo.KEY);
|
||||
if (!tag.hasKey(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.hasKey(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.server.v1_16_R2.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getOrCreateTag();
|
||||
tag.setInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.setFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.setIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.server.v1_16_R2.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getTag();
|
||||
if (tag == null || !tag.hasKey(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getInt(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.getInt(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.server.v1_16_R2.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getOrCreateTag();
|
||||
tag.setInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.setInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.server.v1_16_R2.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == 9) {
|
||||
NBTTagList loreLineTagList = displayTag.getList("Lore", 8);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.getString(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
TranslatableComponent translatableComponent = new TranslatableComponent("effect.minecraft.invisibility");
|
||||
translatableComponent.setColor(ChatColor.GRAY);
|
||||
translatableComponent.setItalic(false);
|
||||
loreLines.add(0, ComponentSerializer.toString(translatableComponent));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.set("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.inventory;
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.server.v1_16_R2.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.pickup(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.activeContainer.c();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
WorldServer nmsWorld = ((CraftWorld) world).getHandle();
|
||||
MinecraftKey key = nmsWorld.getTypeKey().a();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_16_4</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.16.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.16.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,424 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import com.loohp.imageframe.utils.UnsafeAccessor;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.minecraft.server.v1_16_R3.ChunkProviderServer;
|
||||
import net.minecraft.server.v1_16_R3.DataWatcher;
|
||||
import net.minecraft.server.v1_16_R3.DataWatcherObject;
|
||||
import net.minecraft.server.v1_16_R3.EntityHuman;
|
||||
import net.minecraft.server.v1_16_R3.EntityItemFrame;
|
||||
import net.minecraft.server.v1_16_R3.EntityPlayer;
|
||||
import net.minecraft.server.v1_16_R3.IChatBaseComponent;
|
||||
import net.minecraft.server.v1_16_R3.ItemWorldMap;
|
||||
import net.minecraft.server.v1_16_R3.MapIcon;
|
||||
import net.minecraft.server.v1_16_R3.MinecraftKey;
|
||||
import net.minecraft.server.v1_16_R3.NBTTagCompound;
|
||||
import net.minecraft.server.v1_16_R3.NBTTagList;
|
||||
import net.minecraft.server.v1_16_R3.NBTTagString;
|
||||
import net.minecraft.server.v1_16_R3.Packet;
|
||||
import net.minecraft.server.v1_16_R3.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.server.v1_16_R3.PacketPlayOutMap;
|
||||
import net.minecraft.server.v1_16_R3.PersistentIdCounts;
|
||||
import net.minecraft.server.v1_16_R3.PlayerChunkMap;
|
||||
import net.minecraft.server.v1_16_R3.PlayerInventory;
|
||||
import net.minecraft.server.v1_16_R3.ResourceKey;
|
||||
import net.minecraft.server.v1_16_R3.WorldMap;
|
||||
import net.minecraft.server.v1_16_R3.WorldServer;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import org.bukkit.craftbukkit.v1_16_R3.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_16_R3.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_16_R3.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_16_R3.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_16_R3.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_16_R3.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_16_4 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field nmsMapIconTypeDisplayField;
|
||||
private final Field[] nmsPacketPlayOutEntityMetadataFields;
|
||||
private final Field nmsItemFrameItemStackDataWatcherField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_16_4() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.server.v1_16_R3.Entity.class.getDeclaredField("S");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
nmsMapIconTypeDisplayField = MapIcon.Type.class.getDeclaredField("C");
|
||||
nmsPacketPlayOutEntityMetadataFields = PacketPlayOutEntityMetadata.class.getDeclaredFields();
|
||||
nmsItemFrameItemStackDataWatcherField = EntityItemFrame.class.getDeclaredField("ITEM");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("a");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.colors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.humans;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.humans;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
try {
|
||||
nmsMapIconTypeDisplayField.setAccessible(true);
|
||||
return nmsMapIconTypeDisplayField.getBoolean(mapIconType);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"unchecked", "resource"})
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.getMinecraftServer().E().getWorldPersistentData().a(PersistentIdCounts::new, "idcounts");
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked", "resource"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.server.v1_16_R3.World> worldTypeKey = worldServer.getDimensionKey();
|
||||
String mapId = ItemWorldMap.a(id);
|
||||
WorldMap worldMap = new WorldMap(mapId);
|
||||
worldMap.a(spawnLocation.getBlockX(), spawnLocation.getBlockZ(), 3, false, false, worldTypeKey);
|
||||
worldServer.a(worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.getMinecraftServer().E().getWorldPersistentData().a(PersistentIdCounts::new, "idcounts");
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.getChunkProvider();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.playerChunkMap;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.trackedEntities;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (EntityPlayer player : entityTracker.trackedPlayers) {
|
||||
players.add(player.getBukkitEntity());
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? Collections.emptyList() : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
byte[] colors0;
|
||||
int size;
|
||||
if (colors == null) {
|
||||
colors0 = EMPTY_BYTE_ARRAY;
|
||||
size = 0;
|
||||
} else {
|
||||
colors0 = colors;
|
||||
size = 128;
|
||||
}
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, false, mapIcons, colors0, 0, 0, size, size);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameItemStackDataWatcherField.setAccessible(true);
|
||||
DataWatcherObject<net.minecraft.server.v1_16_R3.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.server.v1_16_R3.ItemStack>) nmsItemFrameItemStackDataWatcherField.get(null);
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(dataWatcherObject, CraftItemStack.asNMSCopy(itemStack)));
|
||||
PacketPlayOutEntityMetadata packet = new PacketPlayOutEntityMetadata();
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.server.v1_16_R3.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.getDataWatcher();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(byteField, value));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().playerConnection.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.server.v1_16_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getTag();
|
||||
if (tag == null || !tag.hasKey(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getInt(CombinedMapItemInfo.KEY);
|
||||
if (!tag.hasKey(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.hasKey(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.server.v1_16_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getOrCreateTag();
|
||||
tag.setInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.setFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.setIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.server.v1_16_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getTag();
|
||||
if (tag == null || !tag.hasKey(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getInt(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.getInt(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.server.v1_16_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getOrCreateTag();
|
||||
tag.setInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.setInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.server.v1_16_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == 9) {
|
||||
NBTTagList loreLineTagList = displayTag.getList("Lore", 8);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.getString(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
TranslatableComponent translatableComponent = new TranslatableComponent("effect.minecraft.invisibility");
|
||||
translatableComponent.setColor(ChatColor.GRAY);
|
||||
translatableComponent.setItalic(false);
|
||||
loreLines.add(0, ComponentSerializer.toString(translatableComponent));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.set("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.inventory;
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.server.v1_16_R3.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.pickup(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.activeContainer.c();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
WorldServer nmsWorld = ((CraftWorld) world).getHandle();
|
||||
MinecraftKey key = nmsWorld.getTypeKey().a();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_17</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.17.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.17.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import com.loohp.imageframe.utils.UnsafeAccessor;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.MinecraftKey;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import org.bukkit.craftbukkit.libs.it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import org.bukkit.craftbukkit.v1_17_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_17_R1.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_17_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_17_R1.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_17_R1.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_17_R1.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_17 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field[] nmsPacketPlayOutEntityMetadataFields;
|
||||
private final Field nmsItemFrameItemStackDataWatcherField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_17() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("Z");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
nmsPacketPlayOutEntityMetadataFields = PacketPlayOutEntityMetadata.class.getDeclaredFields();
|
||||
nmsItemFrameItemStackDataWatcherField = EntityItemFrame.class.getDeclaredField("ao");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.b();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.getMinecraftServer().E().getWorldPersistentData().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked", "resource"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.getDimensionKey();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.getMinecraftServer().E().getWorldPersistentData().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.getChunkProvider();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.a;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.G;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (ServerPlayerConnection connection : entityTracker.f) {
|
||||
if (connection instanceof PlayerConnection) {
|
||||
players.add(((PlayerConnection) connection).getPlayer());
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameItemStackDataWatcherField.setAccessible(true);
|
||||
DataWatcherObject<net.minecraft.world.item.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.world.item.ItemStack>) nmsItemFrameItemStackDataWatcherField.get(null);
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(dataWatcherObject, CraftItemStack.asNMSCopy(itemStack)));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.getDataWatcher();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(byteField, value));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().b.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getTag();
|
||||
if (tag == null || !tag.hasKey(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getInt(CombinedMapItemInfo.KEY);
|
||||
if (!tag.hasKey(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.hasKey(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getOrCreateTag();
|
||||
tag.setInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.setFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.setIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getTag();
|
||||
if (tag == null || !tag.hasKey(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getInt(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.getInt(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.getOrCreateTag();
|
||||
tag.setInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.setInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.getList("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.getString(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
TranslatableComponent translatableComponent = new TranslatableComponent("effect.minecraft.invisibility");
|
||||
translatableComponent.setColor(ChatColor.GRAY);
|
||||
translatableComponent.setItalic(false);
|
||||
loreLines.add(0, ComponentSerializer.toString(translatableComponent));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.set("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.getInventory();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.pickup(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bU.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
WorldServer nmsWorld = ((CraftWorld) world).getHandle();
|
||||
MinecraftKey key = nmsWorld.getTypeKey().a();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_18</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.18.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.18.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,412 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import com.loohp.imageframe.utils.UnsafeAccessor;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.MinecraftKey;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_18_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_18_R1.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_18_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_18_R1.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_18_R1.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_18_R1.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_18 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field[] nmsPacketPlayOutEntityMetadataFields;
|
||||
private final Field nmsItemFrameItemStackDataWatcherField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_18() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("aa");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
nmsPacketPlayOutEntityMetadataFields = PacketPlayOutEntityMetadata.class.getDeclaredFields();
|
||||
nmsItemFrameItemStackDataWatcherField = EntityItemFrame.class.getDeclaredField("ap");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.b();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().u().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.aa();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().u().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.k();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.a;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.I;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (ServerPlayerConnection connection : entityTracker.f) {
|
||||
if (connection instanceof PlayerConnection) {
|
||||
players.add(((PlayerConnection) connection).getCraftPlayer());
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameItemStackDataWatcherField.setAccessible(true);
|
||||
DataWatcherObject<net.minecraft.world.item.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.world.item.ItemStack>) nmsItemFrameItemStackDataWatcherField.get(null);
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(dataWatcherObject, CraftItemStack.asNMSCopy(itemStack)));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.ai();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(byteField, value));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().b.a((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.s();
|
||||
if (tag == null || !tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.t();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.s();
|
||||
if (tag == null || !tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.t();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.c("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.j(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
TranslatableComponent translatableComponent = new TranslatableComponent("effect.minecraft.invisibility");
|
||||
translatableComponent.setColor(ChatColor.GRAY);
|
||||
translatableComponent.setItalic(false);
|
||||
loreLines.add(0, ComponentSerializer.toString(translatableComponent));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.a("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fq();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.e(nmsItemStack);
|
||||
if (!added || !nmsItemStack.b()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bW.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
WorldServer nmsWorld = ((CraftWorld) world).getHandle();
|
||||
MinecraftKey key = nmsWorld.getTypeKey().a();
|
||||
return Key.key(key.b(), key.a());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_18_2</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.18.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.18.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,411 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import com.loohp.imageframe.utils.UnsafeAccessor;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.chat.TranslatableComponent;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_18_R2.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_18_R2.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_18_R2.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_18_R2.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_18_2 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field[] nmsPacketPlayOutEntityMetadataFields;
|
||||
private final Field nmsItemFrameItemStackDataWatcherField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_18_2() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("Z");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
nmsPacketPlayOutEntityMetadataFields = PacketPlayOutEntityMetadata.class.getDeclaredFields();
|
||||
nmsItemFrameItemStackDataWatcherField = EntityItemFrame.class.getDeclaredField("ao");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.b();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().t().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.aa();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().t().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.k();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.a;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.J;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (ServerPlayerConnection connection : entityTracker.f) {
|
||||
if (connection instanceof PlayerConnection) {
|
||||
players.add(((PlayerConnection) connection).getCraftPlayer());
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameItemStackDataWatcherField.setAccessible(true);
|
||||
DataWatcherObject<net.minecraft.world.item.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.world.item.ItemStack>) nmsItemFrameItemStackDataWatcherField.get(null);
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(dataWatcherObject, CraftItemStack.asNMSCopy(itemStack)));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.ai();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(byteField, value));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().b.a((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.t();
|
||||
if (tag == null || !tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.u();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.t();
|
||||
if (tag == null || !tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.u();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.c("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.j(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
TranslatableComponent translatableComponent = new TranslatableComponent("effect.minecraft.invisibility");
|
||||
translatableComponent.setColor(ChatColor.GRAY);
|
||||
translatableComponent.setItalic(false);
|
||||
loreLines.add(0, ComponentSerializer.toString(translatableComponent));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.a("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fr();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.e(nmsItemStack);
|
||||
if (!added || !nmsItemStack.b()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bU.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_19</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.19.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.19.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import com.loohp.imageframe.utils.UnsafeAccessor;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_19_R1.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_19 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field[] nmsPacketPlayOutEntityMetadataFields;
|
||||
private final Field nmsItemFrameItemStackDataWatcherField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_19() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("Z");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
nmsPacketPlayOutEntityMetadataFields = PacketPlayOutEntityMetadata.class.getDeclaredFields();
|
||||
nmsItemFrameItemStackDataWatcherField = EntityItemFrame.class.getDeclaredField("ao");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.b();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().C().t().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ab();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().C().t().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.k();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.a;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.K;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (ServerPlayerConnection connection : entityTracker.f) {
|
||||
if (connection instanceof PlayerConnection) {
|
||||
players.add(((PlayerConnection) connection).getCraftPlayer());
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameItemStackDataWatcherField.setAccessible(true);
|
||||
DataWatcherObject<net.minecraft.world.item.ItemStack> dataWatcherObject = (DataWatcherObject<net.minecraft.world.item.ItemStack>) nmsItemFrameItemStackDataWatcherField.get(null);
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(dataWatcherObject, CraftItemStack.asNMSCopy(itemStack)));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.ai();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.Item<?>> dataWatchers = Collections.singletonList(new DataWatcher.Item<>(byteField, value));
|
||||
PacketPlayOutEntityMetadata packet = (PacketPlayOutEntityMetadata) UnsafeAccessor.getUnsafe().allocateInstance(PacketPlayOutEntityMetadata.class);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[0].setInt(packet, entityId);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].setAccessible(true);
|
||||
nmsPacketPlayOutEntityMetadataFields[1].set(packet, dataWatchers);
|
||||
return packet;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().b.a((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.u();
|
||||
if (tag == null || !tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.u();
|
||||
if (tag == null || !tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.c("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.j(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
loreLines.add(0, CraftChatMessage.toJSON(IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false))));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.a("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fA();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.e(nmsItemStack);
|
||||
if (!added || !nmsItemStack.b()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bU.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_19_3</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.19.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.19.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_19_R2.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_19_3 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_19_3() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("Z");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.b();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().C().t().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ac();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().C().t().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.k();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.a;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.L;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (ServerPlayerConnection connection : entityTracker.f) {
|
||||
if (connection instanceof PlayerConnection) {
|
||||
players.add(((PlayerConnection) connection).getCraftPlayer());
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(EntityItemFrame.ao, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.al();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().b.a((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.u();
|
||||
if (tag == null || !tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.u();
|
||||
if (tag == null || !tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.c("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.j(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
loreLines.add(0, CraftChatMessage.toJSON(IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false))));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.a("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fE();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.e(nmsItemStack);
|
||||
if (!added || !nmsItemStack.b()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bU.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_19_4</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.19.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.19.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_19_R3.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_19_4 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_19_4() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("an");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.b();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().s().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ab();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().s().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.k();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.a;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.L;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (ServerPlayerConnection connection : entityTracker.f) {
|
||||
if (connection instanceof PlayerConnection) {
|
||||
players.add(((PlayerConnection) connection).getCraftPlayer());
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(EntityItemFrame.g, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.aj();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().b.a((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.u();
|
||||
if (tag == null || !tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.u();
|
||||
if (tag == null || !tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.c("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.j(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
loreLines.add(0, CraftChatMessage.toJSON(IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false))));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.a("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fJ();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.e(nmsItemStack);
|
||||
if (!added || !nmsItemStack.b()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bP.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_20</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.20.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_20_R1.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_20 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_20() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("an");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.b();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().s().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ac();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().s().a(PersistentIdCounts::b, PersistentIdCounts::new, PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.b();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.k();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.a;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.K;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (ServerPlayerConnection connection : entityTracker.f) {
|
||||
if (connection instanceof PlayerConnection) {
|
||||
players.add(((PlayerConnection) connection).getCraftPlayer());
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(EntityItemFrame.g, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.aj();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.b(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().c.a((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
if (tag == null || !tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.w();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
if (tag == null || !tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.w();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.c("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.j(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
loreLines.add(0, CraftChatMessage.toJSON(IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false))));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.a("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fN();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.e(nmsItemStack);
|
||||
if (!added || !nmsItemStack.b()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bR.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_20_2</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.20.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,385 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ChunkProviderServer;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.PlayerChunkMap;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.server.network.PlayerConnection;
|
||||
import net.minecraft.server.network.ServerPlayerConnection;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_20_R2.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_20_2 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_20_2() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("ao");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.d();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().s().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ac();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.n().D().s().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
WorldServer worldServer = ((CraftWorld) entity.getWorld()).getHandle();
|
||||
ChunkProviderServer chunkProviderServer = worldServer.k();
|
||||
PlayerChunkMap playerChunkMap = chunkProviderServer.a;
|
||||
Int2ObjectMap<PlayerChunkMap.EntityTracker> entityTrackers = playerChunkMap.K;
|
||||
PlayerChunkMap.EntityTracker entityTracker = entityTrackers.get(entity.getEntityId());
|
||||
if (entityTracker == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
Set<Player> players = new HashSet<>();
|
||||
for (ServerPlayerConnection connection : entityTracker.f) {
|
||||
if (connection instanceof PlayerConnection) {
|
||||
players.add(((PlayerConnection) connection).getCraftPlayer());
|
||||
}
|
||||
}
|
||||
return players;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(EntityItemFrame.g, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.al();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.b(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().c.b((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
if (tag == null || !tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.w();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
if (tag == null || !tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.w();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.c("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.j(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
loreLines.add(0, CraftChatMessage.toJSON(IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false))));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.a("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fR();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.e(nmsItemStack);
|
||||
if (!added || !nmsItemStack.b()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bS.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_20_3</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.20.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>papermc</id>
|
||||
<url>https://repo.papermc.io/repository/maven-public/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,365 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.nbt.NBTBase;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.nbt.NBTTagList;
|
||||
import net.minecraft.nbt.NBTTagString;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.ItemWorldMap;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_20_R3.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_20_3 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_20_3() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = net.minecraft.world.entity.Entity.class.getDeclaredField("ao");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = PersistentIdCounts.class.getDeclaredField("b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(mapIconType, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), iChat);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapIcon.Type toNMSMapIconType(MapCursor.Type type) {
|
||||
return MapIcon.Type.a(type.getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
MapIcon.Type mapIconType = toNMSMapIconType(type);
|
||||
return mapIconType.d();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().F().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ae();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
worldServer.a(ItemWorldMap.a(id), worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().F().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(mapId, (byte) 0, false, mapIcons, b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(EntityItemFrame.g, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.an();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.b(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.b<?>> dataWatchers = Collections.singletonList(DataWatcher.b.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().c.b((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
if (tag == null || !tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.w();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.v();
|
||||
if (tag == null || !tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound tag = nmsItemStack.w();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
NBTTagCompound displayTag = nmsItemStack.a("display");
|
||||
List<String> loreLines;
|
||||
if (displayTag.d("Lore") == NBTBase.q) {
|
||||
NBTTagList loreLineTagList = displayTag.c("Lore", NBTBase.p);
|
||||
loreLines = new ArrayList<>(loreLineTagList.size());
|
||||
for (int i = 0; i < loreLineTagList.size(); i++) {
|
||||
loreLines.add(loreLineTagList.j(i));
|
||||
}
|
||||
} else {
|
||||
loreLines = new ArrayList<>(1);
|
||||
}
|
||||
loreLines.add(0, CraftChatMessage.toJSON(IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false))));
|
||||
NBTTagList loreLineTagList = new NBTTagList();
|
||||
for (int i = 0; i < loreLines.size(); i++) {
|
||||
loreLineTagList.b(i, NBTTagString.a(loreLines.get(i)));
|
||||
}
|
||||
displayTag.a("Lore", loreLineTagList);
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fS();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.e(nmsItemStack);
|
||||
if (!added || !nmsItemStack.b()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bS.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_20_5</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.5-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.20.5-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_20_5 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_20_5() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "ap");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, Object2IntMap.class, "usedAuxIds", "b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().I().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.af();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().I().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.g, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.ap();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().c.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.i, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.i, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gc();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.f(nmsItemStack);
|
||||
if (!added || !nmsItemStack.e()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cb.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_20_6</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.6-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.20.6-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_20_R4.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_20_6 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_20_6() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "ap");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, Object2IntMap.class, "usedAuxIds", "b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.o;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().I().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.af();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().I().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.g, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.ap();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().c.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.i, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.i, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gc();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.f(nmsItemStack);
|
||||
if (!added || !nmsItemStack.e()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cb.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_21() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "ap");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, Object2IntMap.class, "usedAuxIds", "b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().I().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.af();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().I().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.f, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.ar();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().c.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.i, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.i, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fY();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.f(nmsItemStack);
|
||||
if (!added || !nmsItemStack.e()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cd.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_1</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R1.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_1 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_21_1() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "ap");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, Object2IntMap.class, "usedAuxIds", "b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().I().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.af();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.o().I().u().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.b b = colors == null ? null : new WorldMap.b(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(b));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.f, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.ar();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().c.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.i, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.i, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.fY();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.f(nmsItemStack);
|
||||
if (!added || !nmsItemStack.e()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cd.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_10</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.10-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.10-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_10 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V1_21_10() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "aA");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, int.class, "lastMapId", "d");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, AtomicInteger.class, "lastMapId", "d");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.h = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().O().y().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(persistentIdCounts) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.al();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().O().y().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(persistentIdCounts);
|
||||
persistentIdCountsLastMapIdField.setInt(persistentIdCounts, Math.max(lastMapId, id));
|
||||
}
|
||||
persistentIdCounts.t();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.c, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.aC();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().g.b((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.b();
|
||||
if (!tag.b(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.b(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.k(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.b();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.b();
|
||||
if (!tag.b(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.b(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.b();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gB();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.g(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cl.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_11</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.11-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.11-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,391 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R7.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R7.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R7.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R7.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R7.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R7.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R7.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_11 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V1_21_11() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "aA");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, int.class, "lastMapId", "d");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, AtomicInteger.class, "lastMapId", "d");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.h = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.s().N().A().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(persistentIdCounts) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "DataFlowIssue"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.aq();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.s().N().A().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(persistentIdCounts);
|
||||
persistentIdCountsLastMapIdField.setInt(persistentIdCounts, Math.max(lastMapId, id));
|
||||
}
|
||||
persistentIdCounts.u();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.c, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.aD();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().g.b((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.b();
|
||||
if (!tag.b(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.b(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.k(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.b();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.b();
|
||||
if (!tag.b(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.b(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.b();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.m, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.m, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gK();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.g(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cn.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_2</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,370 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_2 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_21_2() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "am");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, Object2IntMap.class, "usedAuxIds", "b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.p().J().v().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ah();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.p().J().v().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.e, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.au();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().f.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gi();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.f(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cd.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_3</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R2.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_3 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
|
||||
public V1_21_3() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "am");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, Object2IntMap.class, "usedAuxIds", "b");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.p().J().v().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ah();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.p().J().v().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, renderData.cursors);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.e, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.au();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().f.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.c();
|
||||
if (!tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.c();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gi();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.f(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cd.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_4</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.4-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,378 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R3.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_4 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsUsedAuxIdsField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V1_21_4() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "am");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
persistentIdCountsUsedAuxIdsField = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, Object2IntMap.class, "usedAuxIds", "b");
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.g = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.p;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.p().J().w().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
return usedAuxIds.getInt("map") + 1;
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({"deprecation", "unchecked"})
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsUsedAuxIdsField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.ai();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.p().J().w().a(PersistentIdCounts.a(), PersistentIdCounts.a);
|
||||
Object2IntMap<String> usedAuxIds = (Object2IntMap<String>) persistentIdCountsUsedAuxIdsField.get(persistentIdCounts);
|
||||
int freeAuxValue = usedAuxIds.getInt("map");
|
||||
if (freeAuxValue < id) {
|
||||
usedAuxIds.put("map", id);
|
||||
persistentIdCounts.c();
|
||||
}
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.e, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.au();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().f.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.e(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(CombinedMapItemInfo.KEY);
|
||||
if (!tag.e(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.e(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.j(CombinedMapItemInfo.PLACEMENT_YAW_KEY);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.n(CombinedMapItemInfo.PLACEMENT_UUID_KEY));
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.e(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.h(FilledMapItemInfo.KEY);
|
||||
int mapPartIndex = tag.h(FilledMapItemInfo.INDEX_KEY);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gi();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.f(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cd.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_5</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.5-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.5-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R4.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_5 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V1_21_5() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "am");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, int.class, "lastMapId", "d");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, AtomicInteger.class, "lastMapId", "d");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.h = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.p().J().w().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(persistentIdCounts) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.aj();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.p().J().w().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(persistentIdCounts);
|
||||
persistentIdCountsLastMapIdField.setInt(persistentIdCounts, Math.max(lastMapId, id));
|
||||
}
|
||||
persistentIdCounts.f();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.e, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.ar();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().f.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.b(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.b(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.k(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.b(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.b(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gj();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.g(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.bR.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_6</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.6-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.6-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_6 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V1_21_6() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "az");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, int.class, "lastMapId", "d");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, AtomicInteger.class, "lastMapId", "d");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.h = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().J().x().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(persistentIdCounts) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.aj();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().J().x().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(persistentIdCounts);
|
||||
persistentIdCountsLastMapIdField.setInt(persistentIdCounts, Math.max(lastMapId, id));
|
||||
}
|
||||
persistentIdCounts.e();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.d, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.au();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().g.sendPacket((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.b(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.b(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.k(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.b(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.b(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gs();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.g(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cn.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_7</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.7-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.7-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_7 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V1_21_7() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "az");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, int.class, "lastMapId", "d");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, AtomicInteger.class, "lastMapId", "d");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.h = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().J().x().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(persistentIdCounts) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.aj();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().J().x().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(persistentIdCounts);
|
||||
persistentIdCountsLastMapIdField.setInt(persistentIdCounts, Math.max(lastMapId, id));
|
||||
}
|
||||
persistentIdCounts.e();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.d, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.au();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().g.b((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.b(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.b(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.k(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.b(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.b(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gs();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.g(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cn.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_8</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.8-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.8-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R5.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_8 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V1_21_8() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "az");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, int.class, "lastMapId", "d");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, AtomicInteger.class, "lastMapId", "d");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.h = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().J().x().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(persistentIdCounts) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.aj();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().J().x().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(persistentIdCounts);
|
||||
persistentIdCountsLastMapIdField.setInt(persistentIdCounts, Math.max(lastMapId, id));
|
||||
}
|
||||
persistentIdCounts.e();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.d, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.au();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().g.b((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.b(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.b(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.k(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.d();
|
||||
if (!tag.b(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.b(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.d();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gs();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.g(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cn.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V1_21_9</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.21.9-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.21.9-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,390 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.EnumChatFormat;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.NBTTagCompound;
|
||||
import net.minecraft.network.chat.ChatModifier;
|
||||
import net.minecraft.network.chat.IChatBaseComponent;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutEntityMetadata;
|
||||
import net.minecraft.network.protocol.game.PacketPlayOutMap;
|
||||
import net.minecraft.network.syncher.DataWatcher;
|
||||
import net.minecraft.network.syncher.DataWatcherObject;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.EntityPlayer;
|
||||
import net.minecraft.server.level.WorldServer;
|
||||
import net.minecraft.world.entity.decoration.EntityItemFrame;
|
||||
import net.minecraft.world.entity.player.EntityHuman;
|
||||
import net.minecraft.world.entity.player.PlayerInventory;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIcon;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.PersistentIdCounts;
|
||||
import net.minecraft.world.level.saveddata.maps.WorldMap;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.CraftWorld;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.map.RenderData;
|
||||
import org.bukkit.craftbukkit.v1_21_R6.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V1_21_9 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V1_21_9() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, DataWatcherObject.class, "DATA_SHARED_FLAGS_ID", "aA");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, int.class, "lastMapId", "d");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(PersistentIdCounts.class, AtomicInteger.class, "lastMapId", "d");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public WorldMap getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (WorldMap) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.h = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
WorldMap nmsWorldMap = getWorldMap(mapView);
|
||||
Map<EntityHuman, WorldMap.WorldMapHumanTracker> humansMap = nmsWorldMap.q;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapIcon toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
IChatBaseComponent iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapIcon(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.a().c();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().O().y().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(persistentIdCounts) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
WorldServer worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<net.minecraft.world.level.World> worldTypeKey = worldServer.al();
|
||||
WorldMap worldMap = WorldMap.a(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, worldTypeKey);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.a(mapId, worldMap);
|
||||
PersistentIdCounts persistentIdCounts = worldServer.q().O().y().a(PersistentIdCounts.b);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(persistentIdCounts);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(persistentIdCounts);
|
||||
persistentIdCountsLastMapIdField.setInt(persistentIdCounts, Math.max(lastMapId, id));
|
||||
}
|
||||
persistentIdCounts.t();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutMap createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapIcon> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
WorldMap.c c = colors == null ? null : new WorldMap.c(0, 0, 128, 128, colors);
|
||||
return new PacketPlayOutMap(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(c));
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketPlayOutEntityMetadata createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(EntityItemFrame.c, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
DataWatcher watcher = nmsEntity.aC();
|
||||
DataWatcherObject<Byte> byteField = (DataWatcherObject<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.a(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<DataWatcher.c<?>> dataWatchers = Collections.singletonList(DataWatcher.c.a(byteField, value));
|
||||
return new PacketPlayOutEntityMetadata(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().g.b((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.b();
|
||||
if (!tag.b(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.b(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.b(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.k(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.b();
|
||||
tag.a(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.a(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
NBTTagCompound tag = customData.b();
|
||||
if (!tag.b(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.b(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.b(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.a(DataComponents.b, CustomData.a);
|
||||
NBTTagCompound tag = customData.b();
|
||||
tag.a(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.a(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.b, CustomData.a(tag)).a());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.a(DataComponents.j, ItemLore.a);
|
||||
List<IChatBaseComponent> loreLines = new ArrayList<>(itemLore.a());
|
||||
loreLines.add(0, IChatBaseComponent.c("effect.minecraft.invisibility").c(ChatModifier.a.c(EnumChatFormat.h).b(false)));
|
||||
nmsItemStack.b(DataComponentPatch.a().a(DataComponents.j, new ItemLore(loreLines)).a());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
EntityPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
PlayerInventory inventory = nmsPlayer.gB();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.g(nmsItemStack);
|
||||
if (!added || !nmsItemStack.f()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.cl.d();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V26_1</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>26.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>26.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecoration;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIndex;
|
||||
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.map.RenderData;
|
||||
import org.bukkit.craftbukkit.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V26_1 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V26_1() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, EntityDataAccessor.class, "DATA_SHARED_FLAGS_ID");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, int.class, "lastMapId");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, AtomicInteger.class, "lastMapId");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public MapItemSavedData getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (MapItemSavedData) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.colors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapDecoration toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
net.minecraft.network.chat.Component iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapDecoration(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.value().showOnItemFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(mapIndex) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<Level> dimension = worldServer.dimension();
|
||||
MapItemSavedData mapItemSavedData = MapItemSavedData.createFresh(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, dimension);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.setMapData(mapId, mapItemSavedData);
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(mapIndex);
|
||||
persistentIdCountsLastMapIdField.setInt(mapIndex, Math.max(lastMapId, id));
|
||||
}
|
||||
mapIndex.setDirty();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundMapItemDataPacket createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapDecoration> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
MapItemSavedData.MapPatch mapPatch = colors == null ? null : new MapItemSavedData.MapPatch(0, 0, 128, 128, colors);
|
||||
return new ClientboundMapItemDataPacket(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(mapPatch));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundSetEntityDataPacket createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(net.minecraft.world.entity.decoration.ItemFrame.DATA_ITEM, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
SynchedEntityData watcher = nmsEntity.getEntityData();
|
||||
EntityDataAccessor<Byte> byteField = (EntityDataAccessor<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(byteField, value));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().connection.send((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.contains(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.contains(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloatOr(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.putFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.putIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.getIntOr(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.putInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.getOrDefault(DataComponents.LORE, ItemLore.EMPTY);
|
||||
List<net.minecraft.network.chat.Component> loreLines = new ArrayList<>(itemLore.lines());
|
||||
loreLines.add(0, net.minecraft.network.chat.Component.translatable("effect.minecraft.invisibility").withStyle(net.minecraft.network.chat.Style.EMPTY.withColor(ChatFormatting.GRAY).withItalic(false)));
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.LORE, new ItemLore(loreLines)).build());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
net.minecraft.world.entity.player.Inventory inventory = nmsPlayer.getInventory();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.add(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.containerMenu.broadcastChanges();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V26_1_1</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>26.1.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>26.1.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecoration;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIndex;
|
||||
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.map.RenderData;
|
||||
import org.bukkit.craftbukkit.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V26_1_1 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V26_1_1() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, EntityDataAccessor.class, "DATA_SHARED_FLAGS_ID");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, int.class, "lastMapId");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, AtomicInteger.class, "lastMapId");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public MapItemSavedData getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (MapItemSavedData) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.colors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapDecoration toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
net.minecraft.network.chat.Component iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapDecoration(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.value().showOnItemFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(mapIndex) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<Level> dimension = worldServer.dimension();
|
||||
MapItemSavedData mapItemSavedData = MapItemSavedData.createFresh(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, dimension);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.setMapData(mapId, mapItemSavedData);
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(mapIndex);
|
||||
persistentIdCountsLastMapIdField.setInt(mapIndex, Math.max(lastMapId, id));
|
||||
}
|
||||
mapIndex.setDirty();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundMapItemDataPacket createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapDecoration> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
MapItemSavedData.MapPatch mapPatch = colors == null ? null : new MapItemSavedData.MapPatch(0, 0, 128, 128, colors);
|
||||
return new ClientboundMapItemDataPacket(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(mapPatch));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundSetEntityDataPacket createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(net.minecraft.world.entity.decoration.ItemFrame.DATA_ITEM, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
SynchedEntityData watcher = nmsEntity.getEntityData();
|
||||
EntityDataAccessor<Byte> byteField = (EntityDataAccessor<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(byteField, value));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().connection.send((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.contains(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.contains(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloatOr(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.putFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.putIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.getIntOr(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.putInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.getOrDefault(DataComponents.LORE, ItemLore.EMPTY);
|
||||
List<net.minecraft.network.chat.Component> loreLines = new ArrayList<>(itemLore.lines());
|
||||
loreLines.add(0, net.minecraft.network.chat.Component.translatable("effect.minecraft.invisibility").withStyle(net.minecraft.network.chat.Style.EMPTY.withColor(ChatFormatting.GRAY).withItalic(false)));
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.LORE, new ItemLore(loreLines)).build());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
net.minecraft.world.entity.player.Inventory inventory = nmsPlayer.getInventory();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.add(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.containerMenu.broadcastChanges();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V26_1_2</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>26.1.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>26.1.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,386 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecoration;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIndex;
|
||||
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.map.RenderData;
|
||||
import org.bukkit.craftbukkit.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V26_1_2 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V26_1_2() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, EntityDataAccessor.class, "DATA_SHARED_FLAGS_ID");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, int.class, "lastMapId");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, AtomicInteger.class, "lastMapId");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public MapItemSavedData getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (MapItemSavedData) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.colors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapDecoration toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
net.minecraft.network.chat.Component iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapDecoration(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.value().showOnItemFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(mapIndex) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<Level> dimension = worldServer.dimension();
|
||||
MapItemSavedData mapItemSavedData = MapItemSavedData.createFresh(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, dimension);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.setMapData(mapId, mapItemSavedData);
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(mapIndex);
|
||||
persistentIdCountsLastMapIdField.setInt(mapIndex, Math.max(lastMapId, id));
|
||||
}
|
||||
mapIndex.setDirty();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundMapItemDataPacket createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapDecoration> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
MapItemSavedData.MapPatch mapPatch = colors == null ? null : new MapItemSavedData.MapPatch(0, 0, 128, 128, colors);
|
||||
return new ClientboundMapItemDataPacket(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(mapPatch));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundSetEntityDataPacket createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(net.minecraft.world.entity.decoration.ItemFrame.DATA_ITEM, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
SynchedEntityData watcher = nmsEntity.getEntityData();
|
||||
EntityDataAccessor<Byte> byteField = (EntityDataAccessor<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(byteField, value));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().connection.send((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.contains(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.contains(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloatOr(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.putFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.putIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.getIntOr(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.putInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.getOrDefault(DataComponents.LORE, ItemLore.EMPTY);
|
||||
List<net.minecraft.network.chat.Component> loreLines = new ArrayList<>(itemLore.lines());
|
||||
loreLines.add(0, net.minecraft.network.chat.Component.translatable("effect.minecraft.invisibility").withStyle(net.minecraft.network.chat.Style.EMPTY.withColor(ChatFormatting.GRAY).withItalic(false)));
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.LORE, new ItemLore(loreLines)).build());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
net.minecraft.world.entity.player.Inventory inventory = nmsPlayer.getInventory();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.add(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.containerMenu.broadcastChanges();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V26_2</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>26.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>26.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,395 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecoration;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIndex;
|
||||
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.map.RenderData;
|
||||
import org.bukkit.craftbukkit.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V26_2 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field nmsItemFrameDataItemField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
|
||||
public V26_2() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, EntityDataAccessor.class, "DATA_SHARED_FLAGS_ID");
|
||||
nmsItemFrameDataItemField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.decoration.ItemFrame.class, EntityDataAccessor.class, "DATA_ITEM");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, int.class, "lastMapId");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, AtomicInteger.class, "lastMapId");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public MapItemSavedData getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (MapItemSavedData) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.colors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapDecoration toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
net.minecraft.network.chat.Component iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapDecoration(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.value().showOnItemFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(mapIndex) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<Level> dimension = worldServer.dimension();
|
||||
MapItemSavedData mapItemSavedData = MapItemSavedData.createFresh(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, dimension);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.setMapData(mapId, mapItemSavedData);
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(mapIndex);
|
||||
persistentIdCountsLastMapIdField.setInt(mapIndex, Math.max(lastMapId, id));
|
||||
}
|
||||
mapIndex.setDirty();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundMapItemDataPacket createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapDecoration> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
MapItemSavedData.MapPatch mapPatch = colors == null ? null : new MapItemSavedData.MapPatch(0, 0, 128, 128, colors);
|
||||
return new ClientboundMapItemDataPacket(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(mapPatch));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundSetEntityDataPacket createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameDataItemField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
EntityDataAccessor<net.minecraft.world.item.ItemStack> dataItemAccessor = (EntityDataAccessor<net.minecraft.world.item.ItemStack>) nmsItemFrameDataItemField.get(null);
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(dataItemAccessor, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
SynchedEntityData watcher = nmsEntity.getEntityData();
|
||||
EntityDataAccessor<Byte> byteField = (EntityDataAccessor<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(byteField, value));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().connection.send((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.contains(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.contains(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloatOr(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.putFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.putIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.getIntOr(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.putInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.getOrDefault(DataComponents.LORE, ItemLore.EMPTY);
|
||||
List<net.minecraft.network.chat.Component> loreLines = new ArrayList<>(itemLore.lines());
|
||||
loreLines.add(0, net.minecraft.network.chat.Component.translatable("effect.minecraft.invisibility").withStyle(net.minecraft.network.chat.Style.EMPTY.withColor(ChatFormatting.GRAY).withItalic(false)));
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.LORE, new ItemLore(loreLines)).build());
|
||||
ItemStack modified = CraftItemStack.asCraftMirror(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
net.minecraft.world.entity.player.Inventory inventory = nmsPlayer.getInventory();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.add(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(CraftItemStack.asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.containerMenu.broadcastChanges();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-V26_3</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>26.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>26.3-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,414 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.google.common.collect.Collections2;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import com.loohp.imageframe.utils.ReflectionUtils;
|
||||
import com.loohp.imageframe.utils.UUIDUtils;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.Holder;
|
||||
import net.minecraft.core.component.DataComponentPatch;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.nbt.CompoundTag;
|
||||
import net.minecraft.network.protocol.Packet;
|
||||
import net.minecraft.network.protocol.game.ClientboundMapItemDataPacket;
|
||||
import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket;
|
||||
import net.minecraft.network.syncher.EntityDataAccessor;
|
||||
import net.minecraft.network.syncher.SynchedEntityData;
|
||||
import net.minecraft.resources.ResourceKey;
|
||||
import net.minecraft.server.level.ServerLevel;
|
||||
import net.minecraft.server.level.ServerPlayer;
|
||||
import net.minecraft.world.item.ItemInstance;
|
||||
import net.minecraft.world.item.component.CustomData;
|
||||
import net.minecraft.world.item.component.ItemLore;
|
||||
import net.minecraft.world.level.Level;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecoration;
|
||||
import net.minecraft.world.level.saveddata.maps.MapDecorationType;
|
||||
import net.minecraft.world.level.saveddata.maps.MapId;
|
||||
import net.minecraft.world.level.saveddata.maps.MapIndex;
|
||||
import net.minecraft.world.level.saveddata.maps.MapItemSavedData;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.craftbukkit.CraftWorld;
|
||||
import org.bukkit.craftbukkit.entity.CraftEntity;
|
||||
import org.bukkit.craftbukkit.entity.CraftPlayer;
|
||||
import org.bukkit.craftbukkit.inventory.CraftItemStack;
|
||||
import org.bukkit.craftbukkit.map.CraftMapCursor;
|
||||
import org.bukkit.craftbukkit.map.CraftMapView;
|
||||
import org.bukkit.craftbukkit.map.RenderData;
|
||||
import org.bukkit.craftbukkit.util.CraftChatMessage;
|
||||
import org.bukkit.enchantments.Enchantment;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemFlag;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class V26_3 extends NMSWrapper {
|
||||
|
||||
private final Field nmsEntityByteDataWatcherField;
|
||||
private final Field nmsItemFrameDataItemField;
|
||||
private final Field craftMapViewWorldMapField;
|
||||
private final Field persistentIdCountsLastMapIdField;
|
||||
private final Field renderDataCursorsField;
|
||||
private final Method asBukkitCopyMethod;
|
||||
|
||||
public V26_3() {
|
||||
try {
|
||||
nmsEntityByteDataWatcherField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.Entity.class, EntityDataAccessor.class, "DATA_SHARED_FLAGS_ID");
|
||||
nmsItemFrameDataItemField = ReflectionUtils.findDeclaredField(net.minecraft.world.entity.decoration.ItemFrame.class, EntityDataAccessor.class, "DATA_ITEM");
|
||||
craftMapViewWorldMapField = CraftMapView.class.getDeclaredField("worldMap");
|
||||
Field persistentIdCountsLastMapIdField0;
|
||||
try {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, int.class, "lastMapId");
|
||||
} catch (NoSuchFieldException e) {
|
||||
persistentIdCountsLastMapIdField0 = ReflectionUtils.findDeclaredField(MapIndex.class, AtomicInteger.class, "lastMapId");
|
||||
}
|
||||
persistentIdCountsLastMapIdField = persistentIdCountsLastMapIdField0;
|
||||
renderDataCursorsField = RenderData.class.getField("cursors");
|
||||
Method asBukkitCopyMethod0;
|
||||
try {
|
||||
asBukkitCopyMethod0 = CraftItemStack.class.getMethod("asCraftMirror", net.minecraft.world.item.ItemStack.class);
|
||||
} catch (NoSuchMethodException e) {
|
||||
asBukkitCopyMethod0 = CraftItemStack.class.getMethod("asBukkitCopy", ItemInstance.class);
|
||||
}
|
||||
asBukkitCopyMethod = asBukkitCopyMethod0;
|
||||
} catch (NoSuchFieldException | NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private ItemStack asBukkitCopy(net.minecraft.world.item.ItemStack nmsItemStack) {
|
||||
try {
|
||||
return (ItemStack) asBukkitCopyMethod.invoke(null, nmsItemStack);
|
||||
} catch (InvocationTargetException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public MapItemSavedData getWorldMap(MapView mapView) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
craftMapViewWorldMapField.setAccessible(true);
|
||||
return (MapItemSavedData) craftMapViewWorldMapField.get(craftMapView);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColors(MapView mapView, byte[] colors) {
|
||||
if (colors.length != COLOR_ARRAY_LENGTH) {
|
||||
throw new IllegalArgumentException("colors array length must be " + COLOR_ARRAY_LENGTH);
|
||||
}
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
nmsWorldMap.colors = colors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return Collections2.transform(humansMap.keySet(), e -> (Player) e.getBukkitEntity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasViewers(MapView mapView) {
|
||||
MapItemSavedData nmsWorldMap = getWorldMap(mapView);
|
||||
Map<net.minecraft.world.entity.player.Player, MapItemSavedData.HoldingPlayer> humansMap = nmsWorldMap.carriedByPlayers;
|
||||
return !humansMap.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapDecoration toNMSMapIcon(MapCursor mapCursor) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(mapCursor.getType());
|
||||
net.minecraft.network.chat.Component iChat = CraftChatMessage.fromStringOrNull(mapCursor.getCaption());
|
||||
return new MapDecoration(decorationTypeHolder, mapCursor.getX(), mapCursor.getY(), mapCursor.getDirection(), Optional.ofNullable(iChat));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Holder<MapDecorationType> toNMSMapIconType(MapCursor.Type type) {
|
||||
return CraftMapCursor.CraftType.bukkitToMinecraftHolder(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRenderOnFrame(MapCursor.Type type) {
|
||||
Holder<MapDecorationType> decorationTypeHolder = toNMSMapIconType(type);
|
||||
return decorationTypeHolder.value().showOnItemFrame();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextAvailableMapId(World world) {
|
||||
try {
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
return atomicInteger.get() + 1;
|
||||
} else {
|
||||
return persistentIdCountsLastMapIdField.getInt(mapIndex) + 1;
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MapView getMapOrCreateMissing(World world, int id) {
|
||||
try {
|
||||
MapView mapView = Bukkit.getMap(id);
|
||||
if (mapView != null) {
|
||||
return mapView;
|
||||
}
|
||||
persistentIdCountsLastMapIdField.setAccessible(true);
|
||||
Location spawnLocation = world.getSpawnLocation();
|
||||
ServerLevel worldServer = ((CraftWorld) world).getHandle();
|
||||
ResourceKey<Level> dimension = worldServer.dimension();
|
||||
MapItemSavedData mapItemSavedData = MapItemSavedData.createFresh(spawnLocation.getX(), spawnLocation.getZ(), (byte) 3, false, false, dimension);
|
||||
MapId mapId = new MapId(id);
|
||||
worldServer.setMapData(mapId, mapItemSavedData);
|
||||
MapIndex mapIndex = worldServer.getServer().getDataStorage().computeIfAbsent(MapIndex.TYPE);
|
||||
if (persistentIdCountsLastMapIdField.getType().equals(AtomicInteger.class)) {
|
||||
AtomicInteger atomicInteger = (AtomicInteger) persistentIdCountsLastMapIdField.get(mapIndex);
|
||||
atomicInteger.getAndUpdate(lastMapId -> Math.max(lastMapId, id));
|
||||
} else {
|
||||
int lastMapId = persistentIdCountsLastMapIdField.getInt(mapIndex);
|
||||
persistentIdCountsLastMapIdField.setInt(mapIndex, Math.max(lastMapId, id));
|
||||
}
|
||||
mapIndex.setDirty();
|
||||
return Bukkit.getMap(id);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player) {
|
||||
try {
|
||||
CraftMapView craftMapView = (CraftMapView) mapView;
|
||||
CraftPlayer craftPlayer = (CraftPlayer) player;
|
||||
RenderData renderData = craftMapView.render(craftPlayer);
|
||||
return new MutablePair<>(renderData.buffer, (List<MapCursor>) renderDataCursorsField.get(renderData));
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Player> getEntityTrackers(Entity entity) {
|
||||
return new HashSet<>(entity.getTrackedBy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundMapItemDataPacket createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors) {
|
||||
List<MapDecoration> mapIcons = cursors == null ? null : cursors.stream().map(this::toNMSMapIcon).collect(Collectors.toList());
|
||||
MapItemSavedData.MapPatch mapPatch = colors == null ? null : new MapItemSavedData.MapPatch(0, 0, 128, 128, colors);
|
||||
return new ClientboundMapItemDataPacket(new MapId(mapId), (byte) 0, false, Optional.ofNullable(mapIcons), Optional.ofNullable(mapPatch));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClientboundSetEntityDataPacket createItemFrameItemChangePacket(int entityId, ItemStack itemStack) {
|
||||
try {
|
||||
nmsItemFrameDataItemField.setAccessible(true);
|
||||
@SuppressWarnings("unchecked")
|
||||
EntityDataAccessor<net.minecraft.world.item.ItemStack> dataItemAccessor = (EntityDataAccessor<net.minecraft.world.item.ItemStack>) nmsItemFrameDataItemField.get(null);
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(dataItemAccessor, CraftItemStack.asNMSCopy(itemStack)));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing) {
|
||||
try {
|
||||
int entityId = entity.getEntityId();
|
||||
net.minecraft.world.entity.Entity nmsEntity = ((CraftEntity) entity).getHandle();
|
||||
|
||||
nmsEntityByteDataWatcherField.setAccessible(true);
|
||||
|
||||
SynchedEntityData watcher = nmsEntity.getEntityData();
|
||||
EntityDataAccessor<Byte> byteField = (EntityDataAccessor<Byte>) nmsEntityByteDataWatcherField.get(null);
|
||||
byte value = watcher.get(byteField);
|
||||
|
||||
if (invisible != null) {
|
||||
if (invisible) {
|
||||
value = (byte) (value | 0x20);
|
||||
} else {
|
||||
value = (byte) (value & ~0x20);
|
||||
}
|
||||
}
|
||||
if (glowing != null) {
|
||||
if (glowing) {
|
||||
value = (byte) (value | 0x40);
|
||||
} else {
|
||||
value = (byte) (value & ~0x40);
|
||||
}
|
||||
}
|
||||
|
||||
List<SynchedEntityData.DataValue<?>> dataWatchers = Collections.singletonList(SynchedEntityData.DataValue.create(byteField, value));
|
||||
return new ClientboundSetEntityDataPacket(entityId, dataWatchers);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPacket(Player player, Object packet) {
|
||||
((CraftPlayer) player).getHandle().connection.send((Packet<?>) packet);
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalGetWithoutIsPresent")
|
||||
@Override
|
||||
public CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(CombinedMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(CombinedMapItemInfo.KEY, -1);
|
||||
if (!tag.contains(CombinedMapItemInfo.PLACEMENT_UUID_KEY) || !tag.contains(CombinedMapItemInfo.PLACEMENT_YAW_KEY)) {
|
||||
return new CombinedMapItemInfo(imageMapIndex);
|
||||
}
|
||||
float yaw = tag.getFloatOr(CombinedMapItemInfo.PLACEMENT_YAW_KEY, 0F);
|
||||
UUID uuid = UUIDUtils.fromIntArray(tag.getIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY).get());
|
||||
return new CombinedMapItemInfo(imageMapIndex, new CombinedMapItemInfo.PlacementInfo(yaw, uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(CombinedMapItemInfo.KEY, combinedMapItemInfo.getImageMapIndex());
|
||||
if (combinedMapItemInfo.hasPlacement()) {
|
||||
CombinedMapItemInfo.PlacementInfo placement = combinedMapItemInfo.getPlacement();
|
||||
tag.putFloat(CombinedMapItemInfo.PLACEMENT_YAW_KEY, placement.getYaw());
|
||||
tag.putIntArray(CombinedMapItemInfo.PLACEMENT_UUID_KEY, UUIDUtils.toIntArray(placement.getUniqueId()));
|
||||
}
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return asBukkitCopy(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.get(DataComponents.CUSTOM_DATA);
|
||||
if (customData == null) {
|
||||
return null;
|
||||
}
|
||||
CompoundTag tag = customData.copyTag();
|
||||
if (!tag.contains(FilledMapItemInfo.KEY)) {
|
||||
return null;
|
||||
}
|
||||
int imageMapIndex = tag.getIntOr(FilledMapItemInfo.KEY, -1);
|
||||
int mapPartIndex = tag.getIntOr(FilledMapItemInfo.INDEX_KEY, -1);
|
||||
return new FilledMapItemInfo(imageMapIndex, mapPartIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
CustomData customData = nmsItemStack.getOrDefault(DataComponents.CUSTOM_DATA, CustomData.EMPTY);
|
||||
CompoundTag tag = customData.copyTag();
|
||||
tag.putInt(FilledMapItemInfo.KEY, filledMapItemInfo.getImageMapIndex());
|
||||
tag.putInt(FilledMapItemInfo.INDEX_KEY, filledMapItemInfo.getMapPartIndex());
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.CUSTOM_DATA, CustomData.of(tag)).build());
|
||||
return asBukkitCopy(nmsItemStack);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ItemStack withInvisibleItemFrameMeta(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
ItemLore itemLore = nmsItemStack.getOrDefault(DataComponents.LORE, ItemLore.EMPTY);
|
||||
List<net.minecraft.network.chat.Component> loreLines = new ArrayList<>(itemLore.lines());
|
||||
loreLines.add(0, net.minecraft.network.chat.Component.translatable("effect.minecraft.invisibility").withStyle(net.minecraft.network.chat.Style.EMPTY.withColor(ChatFormatting.GRAY).withItalic(false)));
|
||||
nmsItemStack.applyComponents(DataComponentPatch.builder().set(DataComponents.LORE, new ItemLore(loreLines)).build());
|
||||
ItemStack modified = asBukkitCopy(nmsItemStack);
|
||||
ItemMeta itemMeta = modified.getItemMeta();
|
||||
if (itemMeta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
itemMeta.addEnchant(Enchantment.LUCK_OF_THE_SEA, 1, true);
|
||||
itemMeta.addItemFlags(ItemFlag.HIDE_ENCHANTS);
|
||||
modified.setItemMeta(itemMeta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks) {
|
||||
List<ItemStack> leftovers = new ArrayList<>();
|
||||
ServerPlayer nmsPlayer = ((CraftPlayer) player).getHandle();
|
||||
net.minecraft.world.entity.player.Inventory inventory = nmsPlayer.getInventory();
|
||||
for (ItemStack itemStack : itemStacks) {
|
||||
net.minecraft.world.item.ItemStack nmsItemStack = CraftItemStack.asNMSCopy(itemStack);
|
||||
boolean added = inventory.add(nmsItemStack);
|
||||
if (!added || !nmsItemStack.isEmpty()) {
|
||||
leftovers.add(asBukkitCopy(nmsItemStack));
|
||||
}
|
||||
}
|
||||
nmsPlayer.containerMenu.broadcastChanges();
|
||||
return leftovers;
|
||||
}
|
||||
|
||||
@SuppressWarnings("PatternValidation")
|
||||
public Key getWorldNamespacedKey(World world) {
|
||||
NamespacedKey key = world.getKey();
|
||||
return Key.key(key.getNamespace(), key.getKey());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Parent</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.16.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bukkit</groupId>
|
||||
<artifactId>craftbukkit</artifactId>
|
||||
<version>1.16.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.nms;
|
||||
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.FilledMapItemInfo;
|
||||
import com.loohp.imageframe.objectholders.MutablePair;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.map.MapCursor;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public abstract class NMSWrapper {
|
||||
|
||||
public static final int COLOR_ARRAY_LENGTH = 16384;
|
||||
public static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
|
||||
|
||||
public abstract void setColors(MapView mapView, byte[] colors);
|
||||
|
||||
public abstract Collection<Player> getViewers(MapView mapView);
|
||||
|
||||
public abstract boolean hasViewers(MapView mapView);
|
||||
|
||||
public abstract Object toNMSMapIcon(MapCursor mapCursor);
|
||||
|
||||
public abstract Object toNMSMapIconType(MapCursor.Type type);
|
||||
|
||||
public abstract boolean isRenderOnFrame(MapCursor.Type type);
|
||||
|
||||
public abstract int getNextAvailableMapId(World world);
|
||||
|
||||
public abstract MapView getMapOrCreateMissing(World world, int id);
|
||||
|
||||
public abstract MutablePair<byte[], List<MapCursor>> bukkitRenderMap(MapView mapView, Player player);
|
||||
|
||||
public abstract Set<Player> getEntityTrackers(Entity entity);
|
||||
|
||||
public abstract Object createMapPacket(int mapId, byte[] colors, Collection<MapCursor> cursors);
|
||||
|
||||
public abstract Object createItemFrameItemChangePacket(int entityId, ItemStack itemStack);
|
||||
|
||||
public abstract Object createEntityFlagsPacket(Entity entity, Boolean invisible, Boolean glowing);
|
||||
|
||||
public abstract void sendPacket(Player player, Object packet);
|
||||
|
||||
public abstract CombinedMapItemInfo getCombinedMapItemInfo(ItemStack itemStack);
|
||||
|
||||
public abstract ItemStack withCombinedMapItemInfo(ItemStack itemStack, CombinedMapItemInfo combinedMapItemInfo);
|
||||
|
||||
public abstract FilledMapItemInfo getFilledMapItemInfo(ItemStack itemStack);
|
||||
|
||||
public abstract ItemStack withFilledMapItemInfo(ItemStack itemStack, FilledMapItemInfo filledMapItemInfo);
|
||||
|
||||
public abstract ItemStack withInvisibleItemFrameMeta(ItemStack itemStack);
|
||||
|
||||
public abstract List<ItemStack> giveItems(Player player, List<ItemStack> itemStacks);
|
||||
|
||||
public abstract Key getWorldNamespacedKey(World world);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.objectholders;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class CombinedMapItemInfo {
|
||||
|
||||
public static final String KEY = "CombinedImageMap";
|
||||
public static final String PLACEMENT_YAW_KEY = "CombinedImageMapPlacementYaw";
|
||||
public static final String PLACEMENT_UUID_KEY = "CombinedImageMapPlacementUUID";
|
||||
|
||||
private final int imageMapIndex;
|
||||
private final PlacementInfo placementInfo;
|
||||
|
||||
public CombinedMapItemInfo(int imageMapIndex, PlacementInfo placementInfo) {
|
||||
this.imageMapIndex = imageMapIndex;
|
||||
this.placementInfo = placementInfo;
|
||||
}
|
||||
|
||||
public CombinedMapItemInfo(int imageMapIndex) {
|
||||
this(imageMapIndex, null);
|
||||
}
|
||||
|
||||
public int getImageMapIndex() {
|
||||
return imageMapIndex;
|
||||
}
|
||||
|
||||
public boolean hasPlacement() {
|
||||
return placementInfo != null;
|
||||
}
|
||||
|
||||
public PlacementInfo getPlacement() {
|
||||
return placementInfo;
|
||||
}
|
||||
|
||||
public static class PlacementInfo {
|
||||
|
||||
private final float yaw;
|
||||
private final UUID uuid;
|
||||
|
||||
public PlacementInfo(float yaw, UUID uuid) {
|
||||
this.yaw = yaw;
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public float getYaw() {
|
||||
return yaw;
|
||||
}
|
||||
|
||||
public UUID getUniqueId() {
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.objectholders;
|
||||
|
||||
public class FilledMapItemInfo {
|
||||
|
||||
public static final String KEY = "ImageMap";
|
||||
public static final String INDEX_KEY = "ImageMapPart";
|
||||
|
||||
private final int imageMapIndex;
|
||||
private final int mapPartIndex;
|
||||
|
||||
public FilledMapItemInfo(int imageMapIndex, int mapPartIndex) {
|
||||
this.imageMapIndex = imageMapIndex;
|
||||
this.mapPartIndex = mapPartIndex;
|
||||
}
|
||||
|
||||
public int getImageMapIndex() {
|
||||
return imageMapIndex;
|
||||
}
|
||||
|
||||
public int getMapPartIndex() {
|
||||
return mapPartIndex;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.objectholders;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
public class MutablePair<F, S> {
|
||||
|
||||
private F first;
|
||||
private S second;
|
||||
|
||||
public MutablePair(F first, S second) {
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public void setFirst(F first) {
|
||||
this.first = first;
|
||||
}
|
||||
|
||||
public void setSecond(S second) {
|
||||
this.second = second;
|
||||
}
|
||||
|
||||
public F getFirst() {
|
||||
return first;
|
||||
}
|
||||
|
||||
public S getSecond() {
|
||||
return second;
|
||||
}
|
||||
|
||||
public <T> T product(BiFunction<F, S, T> function) {
|
||||
return function.apply(first, second);
|
||||
}
|
||||
|
||||
public void consume(BiConsumer<F, S> consumer) {
|
||||
consumer.accept(first, second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MutablePair<?, ?> that = (MutablePair<?, ?>) o;
|
||||
return Objects.equals(first, that.first) && Objects.equals(second, that.second);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(first, second);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.utils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ReflectionUtils {
|
||||
|
||||
public static Field findDeclaredField(Class<?> clazz, Class<?> fieldType, String... names) throws NoSuchFieldException {
|
||||
NoSuchFieldException exception = null;
|
||||
for (String name : names) {
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(name);
|
||||
if (field.getType().equals(fieldType)) {
|
||||
return field;
|
||||
}
|
||||
} catch (NoSuchFieldException e) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
if (exception == null) {
|
||||
throw new NoSuchFieldException();
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
public static Method findDeclaredMethod(Class<?> clazz, Class<?>[] parameters, String... names) throws NoSuchMethodException {
|
||||
NoSuchMethodException exception = null;
|
||||
for (String name : names) {
|
||||
try {
|
||||
return clazz.getDeclaredMethod(name, parameters);
|
||||
} catch (NoSuchMethodException e) {
|
||||
exception = e;
|
||||
}
|
||||
}
|
||||
if (exception == null) {
|
||||
throw new NoSuchMethodException();
|
||||
} else {
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.utils;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDUtils {
|
||||
|
||||
public static UUID fromIntArray(int[] array) {
|
||||
if (array.length != 4) {
|
||||
throw new IllegalArgumentException("array length is not 4");
|
||||
}
|
||||
return new UUID((long) array[0] << 32 | (long) array[1] & 4294967295L, (long) array[2] << 32 | (long) array[3] & 4294967295L);
|
||||
}
|
||||
|
||||
public static int[] toIntArray(UUID uuid) {
|
||||
long high = uuid.getMostSignificantBits();
|
||||
long low = uuid.getLeastSignificantBits();
|
||||
return new int[] {(int) (high >>> 32), (int) high, (int) (high >>> low), (int) low};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.utils;
|
||||
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class UnsafeAccessor {
|
||||
|
||||
private static final Field unsafeField;
|
||||
private static Unsafe unsafe;
|
||||
|
||||
static {
|
||||
try {
|
||||
unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Unsafe getUnsafe() {
|
||||
if (unsafe != null) {
|
||||
return unsafe;
|
||||
}
|
||||
try {
|
||||
unsafeField.setAccessible(true);
|
||||
return unsafe = (Unsafe) unsafeField.get(null);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+569
@@ -0,0 +1,569 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ This file is part of ImageFrame.
|
||||
~
|
||||
~ Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
~ Copyright (C) 2026. Contributors
|
||||
~
|
||||
~ This program is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ This program is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame</artifactId>
|
||||
<version>2026.1.5.5</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>ImageFrame</name>
|
||||
<description>Put images on maps and walls!</description>
|
||||
|
||||
<properties>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.build.number></project.build.number>
|
||||
<project.fullVersion>${project.version}</project.fullVersion>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<defaultGoal>clean install</defaultGoal>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<testSourceDirectory>src/test/java</testSourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>true</filtering>
|
||||
<excludes>
|
||||
<exclude>**/*.java</exclude>
|
||||
<exclude>upload/**</exclude>
|
||||
</excludes>
|
||||
</resource>
|
||||
<resource>
|
||||
<directory>src/main/resources</directory>
|
||||
<filtering>false</filtering>
|
||||
<includes>
|
||||
<include>upload/**</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.6.1</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<relocations>
|
||||
<relocation>
|
||||
<pattern>com.loohp.platformscheduler</pattern>
|
||||
<shadedPattern>com.loohp.imageframe.libs.com.loohp.platformscheduler</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.zaxxer.hikari</pattern>
|
||||
<shadedPattern>com.loohp.imageframe.libs.com.zaxxer.hikari</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.simpleyaml</pattern>
|
||||
<shadedPattern>com.loohp.imageframe.libs.org.simpleyaml</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.yaml.snakeyaml</pattern>
|
||||
<shadedPattern>com.loohp.imageframe.libs.org.yaml.snakeyaml</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>com.madgag.gif</pattern>
|
||||
<shadedPattern>com.loohp.imageframe.libs.com.madgag.gif</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>net.kyori</pattern>
|
||||
<shadedPattern>com.loohp.imageframe.libs.net.kyori</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.apache.commons</pattern>
|
||||
<shadedPattern>com.loohp.imageframe.libs.org.apache.commons</shadedPattern>
|
||||
</relocation>
|
||||
<relocation>
|
||||
<pattern>org.jsoup</pattern>
|
||||
<shadedPattern>com.loohp.imageframe.libs.org.jsoup</shadedPattern>
|
||||
</relocation>
|
||||
</relocations>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<failOnError>false</failOnError>
|
||||
<encoding>ISO-8859-1</encoding>
|
||||
<doctitle>ImageFrame JavaDocs</doctitle>
|
||||
<windowtitle>ImageFrame JavaDocs</windowtitle>
|
||||
<additionalOptions>
|
||||
<additionalOption>-Xdoclint:none</additionalOption>
|
||||
</additionalOptions>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>aggregate</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<source>8</source>
|
||||
<target>8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>jenkins</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>env.BUILD_NUMBER</name>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<project.build.number>-b${env.BUILD_NUMBER}</project.build.number>
|
||||
<!--<project.fullVersion>${project.version}${project.build.number}</project.fullVersion> -->
|
||||
<project.fullVersion>${project.version}</project.fullVersion>
|
||||
</properties>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>release-sign-artifacts</id>
|
||||
<activation>
|
||||
<property>
|
||||
<name>performRelease</name>
|
||||
<value>true</value>
|
||||
</property>
|
||||
</activation>
|
||||
<properties>
|
||||
<project.fullVersion>${project.version}</project.fullVersion>
|
||||
</properties>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
<version>3.2.1</version>
|
||||
<configuration>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-sources</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<encoding>${project.build.sourceEncoding}</encoding>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigotmc-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>paper-repo</id>
|
||||
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>sonatype</id>
|
||||
<url>https://oss.sonatype.org/content/groups/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>CodeMC</id>
|
||||
<url>https://repo.codemc.org/repository/maven-public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>viaversion-repo</id>
|
||||
<url>https://repo.viaversion.com</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>loohp-repo</id>
|
||||
<url>https://repo.loohpjames.com/repository</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>placeholderapi</id>
|
||||
<url>https://repo.extendedclip.com/releases/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>opencollab-snapshot</id>
|
||||
<url>https://repo.opencollab.dev/main/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>maven_central</id>
|
||||
<name>Maven Central</name>
|
||||
<url>https://repo.maven.apache.org/maven2/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>7.0.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>PlatformScheduler</artifactId>
|
||||
<version>1.0.0</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.viaversion</groupId>
|
||||
<artifactId>viaversion-api</artifactId>
|
||||
<version>4.5.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.carleslc.Simple-YAML</groupId>
|
||||
<artifactId>Simple-Yaml</artifactId>
|
||||
<version>1.8.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-fileupload</groupId>
|
||||
<artifactId>commons-fileupload</artifactId>
|
||||
<version>1.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.6</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-gson</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-legacy</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.kyori</groupId>
|
||||
<artifactId>adventure-api</artifactId>
|
||||
<version>5.1.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.madgag</groupId>
|
||||
<artifactId>animated-gif-lib</artifactId>
|
||||
<version>1.4</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc.geyser</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>2.11.2-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.geysermc.floodgate</groupId>
|
||||
<artifactId>api</artifactId>
|
||||
<version>2.2.5-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>dev.folia</groupId>
|
||||
<artifactId>folia-api</artifactId>
|
||||
<version>1.20.6-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.6-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.twelvemonkeys.imageio</groupId>
|
||||
<artifactId>imageio-webp</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jsoup</groupId>
|
||||
<artifactId>jsoup</artifactId>
|
||||
<version>1.22.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-Abstraction</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_16</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_16_2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_16_4</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_17</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_18</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_18_2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_19</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_19_3</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_19_4</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_20</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_20_2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_20_3</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_20_5</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_20_6</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_1</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_3</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_4</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_5</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_6</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_7</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_8</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_9</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_10</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V1_21_11</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V26_1</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V26_1_1</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V26_1_2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V26_2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.loohp</groupId>
|
||||
<artifactId>ImageFrame-V26_3</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe;
|
||||
|
||||
import com.loohp.imageframe.config.Config;
|
||||
import com.loohp.imageframe.debug.Debug;
|
||||
import com.loohp.imageframe.hooks.bedrock.BedrockHook;
|
||||
import com.loohp.imageframe.hooks.bedrock.floodgate.FloodgateHookPlatform;
|
||||
import com.loohp.imageframe.hooks.bedrock.geyser.GeyserHookPlatform;
|
||||
import com.loohp.imageframe.invisibleframe.InvisibleFrameManager;
|
||||
import com.loohp.imageframe.language.LanguageManager;
|
||||
import com.loohp.imageframe.listeners.Events;
|
||||
import com.loohp.imageframe.metrics.Charts;
|
||||
import com.loohp.imageframe.metrics.Metrics;
|
||||
import com.loohp.imageframe.objectholders.AnimatedFakeMapManager;
|
||||
import com.loohp.imageframe.objectholders.CombinedMapItemHandler;
|
||||
import com.loohp.imageframe.objectholders.CustomClientNetworkManager;
|
||||
import com.loohp.imageframe.objectholders.IFPlayerManager;
|
||||
import com.loohp.imageframe.objectholders.IFPlayerPreference;
|
||||
import com.loohp.imageframe.objectholders.ImageMap;
|
||||
import com.loohp.imageframe.objectholders.ImageMapAccessPermissionType;
|
||||
import com.loohp.imageframe.objectholders.ImageMapCacheControlMode;
|
||||
import com.loohp.imageframe.objectholders.ImageMapCreationTaskManager;
|
||||
import com.loohp.imageframe.objectholders.ImageMapLoaders;
|
||||
import com.loohp.imageframe.objectholders.ImageMapManager;
|
||||
import com.loohp.imageframe.objectholders.IntRange;
|
||||
import com.loohp.imageframe.objectholders.IntRangeList;
|
||||
import com.loohp.imageframe.objectholders.ItemFrameSelectionManager;
|
||||
import com.loohp.imageframe.objectholders.MapMarkerEditManager;
|
||||
import com.loohp.imageframe.objectholders.RateLimitedPacketSendingManager;
|
||||
import com.loohp.imageframe.objectholders.UnsetState;
|
||||
import com.loohp.imageframe.placeholderapi.Placeholders;
|
||||
import com.loohp.imageframe.storage.ImageFrameStorage;
|
||||
import com.loohp.imageframe.storage.ImageFrameStorageLoaders;
|
||||
import com.loohp.imageframe.updater.Updater;
|
||||
import com.loohp.imageframe.upload.ImageUploadManager;
|
||||
import com.loohp.imageframe.utils.ChatColorUtils;
|
||||
import com.loohp.imageframe.utils.KeyUtils;
|
||||
import com.loohp.imageframe.utils.MCVersion;
|
||||
import com.loohp.imageframe.utils.ModernEventsUtils;
|
||||
import com.loohp.platformscheduler.ScheduledTask;
|
||||
import com.loohp.platformscheduler.Scheduler;
|
||||
import com.twelvemonkeys.imageio.plugins.webp.WebPImageReaderSpi;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.imageio.spi.IIORegistry;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class ImageFrame extends JavaPlugin {
|
||||
|
||||
public static final int BSTATS_PLUGIN_ID = 16773;
|
||||
public static final String CONFIG_ID = "config";
|
||||
|
||||
public static ImageFrame plugin;
|
||||
|
||||
public static MCVersion version;
|
||||
|
||||
public Metrics metrics;
|
||||
|
||||
public static boolean viaHook = false;
|
||||
public static boolean viaDisableSmoothAnimationForLegacyPlayers = false;
|
||||
|
||||
public static boolean bedrockHook = false;
|
||||
public static boolean bedrockDisableSmoothAnimationForBedrockPlayers = false;
|
||||
|
||||
public static boolean debugLogging;
|
||||
|
||||
public static boolean updaterEnabled;
|
||||
public static ScheduledTask updaterTask = null;
|
||||
|
||||
public static String language;
|
||||
|
||||
public static SimpleDateFormat dateFormat;
|
||||
|
||||
public static String mapItemFormat;
|
||||
public static boolean requireEmptyMaps;
|
||||
public static int mapMaxSize;
|
||||
public static boolean restrictImageUrlEnabled;
|
||||
public static List<URL> restrictImageUrls;
|
||||
public static Map<String, Integer> playerCreationLimit;
|
||||
public static int mapMarkerLimit;
|
||||
public static long maxImageFileSize;
|
||||
public static int maxProcessingTime;
|
||||
public static int parallelProcessingLimit;
|
||||
|
||||
public static int rateLimit;
|
||||
|
||||
public static IntRangeList exemptMapIdsFromDeletion;
|
||||
|
||||
public static boolean mapRenderersContextual;
|
||||
public static boolean handleAnimatedMapsOnMainThread;
|
||||
public static boolean sendAnimatedMapsOnMainThread;
|
||||
|
||||
public static ImageMapCacheControlMode<?> cacheControlMode;
|
||||
public static boolean tryDeleteBlankMapFiles;
|
||||
|
||||
public static boolean combinedByDefault;
|
||||
|
||||
public static boolean uploadServiceEnabled;
|
||||
public static String uploadServiceDisplayURL;
|
||||
public static String uploadServiceServerAddress;
|
||||
public static int uploadServiceServerPort;
|
||||
|
||||
public static boolean imageFrameClientEnabled;
|
||||
|
||||
public static int invisibleFrameMaxConversionsPerSplash;
|
||||
public static boolean invisibleFrameGlowEmptyFrames;
|
||||
|
||||
public static Key storageType;
|
||||
public static Map<String, String> storageOptions;
|
||||
|
||||
public static LanguageManager languageManager;
|
||||
public static ImageFrameStorage imageFrameStorage;
|
||||
public static ImageMapManager imageMapManager;
|
||||
public static IFPlayerManager ifPlayerManager;
|
||||
public static ItemFrameSelectionManager itemFrameSelectionManager;
|
||||
public static MapMarkerEditManager mapMarkerEditManager;
|
||||
public static CombinedMapItemHandler combinedMapItemHandler;
|
||||
public static AnimatedFakeMapManager animatedFakeMapManager;
|
||||
public static RateLimitedPacketSendingManager rateLimitedPacketSendingManager;
|
||||
public static InvisibleFrameManager invisibleFrameManager;
|
||||
public static ImageMapCreationTaskManager imageMapCreationTaskManager;
|
||||
public static ImageUploadManager imageUploadManager;
|
||||
public static CustomClientNetworkManager customClientNetworkManager;
|
||||
|
||||
public static boolean isURLAllowed(String link) {
|
||||
if (!restrictImageUrlEnabled) {
|
||||
return true;
|
||||
}
|
||||
if ("upload".equals(link) || imageUploadManager.wasUploaded(link)) {
|
||||
return true;
|
||||
}
|
||||
try {
|
||||
URL url = new URL(link);
|
||||
return restrictImageUrls.stream().anyMatch(whitelisted -> {
|
||||
if (!url.getProtocol().equalsIgnoreCase(whitelisted.getProtocol())) {
|
||||
return false;
|
||||
}
|
||||
if (!url.getHost().equalsIgnoreCase(whitelisted.getHost())) {
|
||||
return false;
|
||||
}
|
||||
return url.getPath().toLowerCase().startsWith(whitelisted.getPath().toLowerCase());
|
||||
});
|
||||
} catch (MalformedURLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static <T extends UnsetState> T getPreferenceUnsetValue(Player player, IFPlayerPreference<T> preference) {
|
||||
String prefix = "imageframe.preference.unsetdefault." + preference.name().toLowerCase() + ".";
|
||||
for (Map.Entry<String, T> entry : preference.getSuggestedValues().entrySet()) {
|
||||
T value = entry.getValue();
|
||||
if (!value.isUnset() && player.hasPermission(prefix + entry.getKey().toLowerCase())) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
return preference.getDefaultValue(ifPlayerManager.getIFPlayer(player.getUniqueId()));
|
||||
}
|
||||
|
||||
public static int getPlayerCreationLimit(Player player) {
|
||||
if (player.hasPermission("imageframe.createlimit.unlimited")) {
|
||||
return -1;
|
||||
}
|
||||
int limit = Integer.MIN_VALUE;
|
||||
for (Map.Entry<String, Integer> entry : playerCreationLimit.entrySet()) {
|
||||
if (player.hasPermission("imageframe.createlimit." + entry.getKey())) {
|
||||
int value = entry.getValue();
|
||||
if (value < 0) {
|
||||
return -1;
|
||||
} else if (value > limit) {
|
||||
limit = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (limit == Integer.MIN_VALUE) {
|
||||
return playerCreationLimit.getOrDefault("default", -1);
|
||||
}
|
||||
return limit;
|
||||
}
|
||||
|
||||
public static boolean hasImageMapPermission(ImageMap imageMap, CommandSender sender, ImageMapAccessPermissionType permissionType) {
|
||||
if (permissionType == null) {
|
||||
return true;
|
||||
}
|
||||
if (sender.hasPermission("imageframe.adminbypass")) {
|
||||
return true;
|
||||
}
|
||||
if (!(sender instanceof Player)) {
|
||||
return false;
|
||||
}
|
||||
return imageMap.getAccessControl().hasPermission(((Player) sender).getUniqueId(), permissionType);
|
||||
}
|
||||
|
||||
public static boolean isPluginEnabled(String name) {
|
||||
return isPluginEnabled(name, true);
|
||||
}
|
||||
|
||||
public static boolean isPluginEnabled(String name, boolean checkRunning) {
|
||||
Plugin plugin = Bukkit.getPluginManager().getPlugin(name);
|
||||
return plugin != null && (!checkRunning || plugin.isEnabled());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
plugin = this;
|
||||
|
||||
version = MCVersion.resolve();
|
||||
|
||||
if (!version.isSupported()) {
|
||||
getServer().getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] This version of minecraft is unsupported! (" + version.toString() + ")");
|
||||
}
|
||||
|
||||
metrics = new Metrics(this, BSTATS_PLUGIN_ID);
|
||||
Charts.setup(metrics);
|
||||
|
||||
getDataFolder().mkdirs();
|
||||
|
||||
if (!ImageIO.getImageReadersByFormatName("webp").hasNext()) {
|
||||
IIORegistry.getDefaultInstance().registerServiceProvider(new WebPImageReaderSpi());
|
||||
}
|
||||
|
||||
try {
|
||||
Config.loadConfig(CONFIG_ID, new File(getDataFolder(), "config.yml"), getClass().getClassLoader().getResourceAsStream("config.yml"), getClass().getClassLoader().getResourceAsStream("config.yml"), true);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
getServer().getPluginManager().disablePlugin(this);
|
||||
return;
|
||||
}
|
||||
reloadConfig();
|
||||
|
||||
getCommand("imageframe").setExecutor(new Commands());
|
||||
|
||||
if (isPluginEnabled("ViaVersion")) {
|
||||
getServer().getConsoleSender().sendMessage(ChatColor.AQUA + "[ImageFrame] ImageFrame has hooked into ViaVersion!");
|
||||
viaHook = true;
|
||||
}
|
||||
if (isPluginEnabled("Geyser-Spigot") || isPluginEnabled("floodgate")) {
|
||||
if (isPluginEnabled("floodgate")) {
|
||||
getServer().getConsoleSender().sendMessage(ChatColor.AQUA + "[ImageFrame] ImageFrame has hooked into Floodgate!");
|
||||
BedrockHook.setBedrockHookPlatform(new FloodgateHookPlatform());
|
||||
} else {
|
||||
getServer().getConsoleSender().sendMessage(ChatColor.AQUA + "[ImageFrame] ImageFrame has hooked into Geyser!");
|
||||
BedrockHook.setBedrockHookPlatform(new GeyserHookPlatform());
|
||||
}
|
||||
bedrockHook = true;
|
||||
}
|
||||
|
||||
getServer().getPluginManager().registerEvents(new Debug(), this);
|
||||
getServer().getPluginManager().registerEvents(new Updater(), this);
|
||||
getServer().getPluginManager().registerEvents(new Events(), this);
|
||||
if (ModernEventsUtils.modernEventsExists()) {
|
||||
getServer().getPluginManager().registerEvents(new Events.ModernEvents(), this);
|
||||
}
|
||||
|
||||
languageManager = new LanguageManager();
|
||||
imageFrameStorage = ImageFrameStorageLoaders.create(storageType, getDataFolder(), storageOptions);
|
||||
imageMapManager = new ImageMapManager(imageFrameStorage);
|
||||
ifPlayerManager = new IFPlayerManager(imageFrameStorage);
|
||||
itemFrameSelectionManager = new ItemFrameSelectionManager();
|
||||
mapMarkerEditManager = new MapMarkerEditManager();
|
||||
combinedMapItemHandler = new CombinedMapItemHandler();
|
||||
animatedFakeMapManager = new AnimatedFakeMapManager();
|
||||
rateLimitedPacketSendingManager = new RateLimitedPacketSendingManager();
|
||||
invisibleFrameManager = new InvisibleFrameManager();
|
||||
imageMapCreationTaskManager = new ImageMapCreationTaskManager(ImageFrame.parallelProcessingLimit);
|
||||
imageUploadManager = new ImageUploadManager(uploadServiceEnabled, uploadServiceServerAddress, uploadServiceServerPort);
|
||||
customClientNetworkManager = new CustomClientNetworkManager(imageFrameClientEnabled);
|
||||
|
||||
if (isPluginEnabled("PlaceholderAPI")) {
|
||||
new Placeholders().register();
|
||||
}
|
||||
|
||||
ImageMapLoaders.init();
|
||||
|
||||
Scheduler.runTaskAsynchronously(this, () -> imageMapManager.loadMaps(ifPlayerManager));
|
||||
|
||||
getServer().getConsoleSender().sendMessage(ChatColor.GREEN + "[ImageFrame] ImageFrame has been Enabled!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (mapMarkerEditManager != null) {
|
||||
mapMarkerEditManager.close();
|
||||
}
|
||||
if (itemFrameSelectionManager != null) {
|
||||
itemFrameSelectionManager.close();
|
||||
}
|
||||
if (imageMapManager != null) {
|
||||
imageMapManager.close();
|
||||
}
|
||||
if (combinedMapItemHandler != null) {
|
||||
combinedMapItemHandler.close();
|
||||
}
|
||||
if (imageUploadManager != null) {
|
||||
imageUploadManager.close();
|
||||
}
|
||||
if (imageFrameStorage != null) {
|
||||
imageFrameStorage.close();
|
||||
}
|
||||
getServer().getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] ImageFrame has been Disabled!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reloadConfig() {
|
||||
Config config = Config.getConfig(CONFIG_ID);
|
||||
config.reload();
|
||||
|
||||
viaDisableSmoothAnimationForLegacyPlayers = config.getConfiguration().getBoolean("Hooks.ViaVersion.DisableSmoothAnimationForLegacyPlayers");
|
||||
bedrockDisableSmoothAnimationForBedrockPlayers = config.getConfiguration().getBoolean("Hooks.Geyser.DisableSmoothAnimationForBedrockPlayers");
|
||||
|
||||
language = config.getConfiguration().getString("Settings.Language");
|
||||
|
||||
dateFormat = new SimpleDateFormat(config.getConfiguration().getString("Settings.DateFormat"));
|
||||
|
||||
mapItemFormat = ChatColorUtils.translateAlternateColorCodes('&', config.getConfiguration().getString("Settings.MapItemFormat"));
|
||||
requireEmptyMaps = config.getConfiguration().getBoolean("Settings.RequireEmptyMaps");
|
||||
mapMaxSize = config.getConfiguration().getInt("Settings.MaxSize");
|
||||
restrictImageUrlEnabled = config.getConfiguration().getBoolean("Settings.RestrictImageUrl.Enabled");
|
||||
restrictImageUrls = config.getConfiguration().getStringList("Settings.RestrictImageUrl.Whitelist").stream().map(s -> {
|
||||
try {
|
||||
return new URL(s);
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}).filter(u -> u != null).collect(Collectors.toList());
|
||||
playerCreationLimit = new HashMap<>();
|
||||
for (String group : config.getConfiguration().getConfigurationSection("Settings.PlayerCreationLimit").getKeys(false)) {
|
||||
playerCreationLimit.put(group, config.getConfiguration().getInt("Settings.PlayerCreationLimit." + group));
|
||||
}
|
||||
mapMarkerLimit = config.getConfiguration().getInt("Settings.MapMarkerLimit");
|
||||
maxImageFileSize = config.getConfiguration().getLong("Settings.MaxImageFileSize");
|
||||
maxProcessingTime = config.getConfiguration().getInt("Settings.MaxProcessingTime");
|
||||
parallelProcessingLimit = config.getConfiguration().getInt("Settings.ParallelProcessingLimit");
|
||||
|
||||
exemptMapIdsFromDeletion = config.getConfiguration().getList("Settings.ExemptMapIdsFromDeletion").stream().map(v -> {
|
||||
try {
|
||||
if (v instanceof Number) {
|
||||
return IntRange.of(((Number) v).intValue());
|
||||
} else {
|
||||
return IntRange.of(v.toString());
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}).filter(v -> v != null).collect(Collectors.toCollection(IntRangeList::new));
|
||||
|
||||
rateLimit = config.getConfiguration().getInt("Settings.MapPacketSendingRateLimit");
|
||||
|
||||
mapRenderersContextual = config.getConfiguration().getBoolean("Settings.MapRenderersContextual");
|
||||
handleAnimatedMapsOnMainThread = config.getConfiguration().getBoolean("Settings.HandleAnimatedMapsOnMainThread");
|
||||
sendAnimatedMapsOnMainThread = config.getConfiguration().getBoolean("Settings.SendAnimatedMapsOnMainThread");
|
||||
|
||||
cacheControlMode = ImageMapCacheControlMode.valueOf(config.getConfiguration().getString("Settings.CacheControlMode"));
|
||||
tryDeleteBlankMapFiles = config.getConfiguration().getBoolean("Settings.TryDeleteBlankMapFiles");
|
||||
|
||||
combinedByDefault = config.getConfiguration().getBoolean("Settings.CombinedByDefault");
|
||||
|
||||
uploadServiceEnabled = config.getConfiguration().getBoolean("UploadService.Enabled");
|
||||
uploadServiceDisplayURL = config.getConfiguration().getString("UploadService.DisplayURL");
|
||||
uploadServiceServerAddress = config.getConfiguration().getString("UploadService.WebServer.Host");
|
||||
uploadServiceServerPort = config.getConfiguration().getInt("UploadService.WebServer.Port");
|
||||
|
||||
imageFrameClientEnabled = config.getConfiguration().getBoolean("ImageFrameClient.Enabled");
|
||||
|
||||
invisibleFrameMaxConversionsPerSplash = config.getConfiguration().getInt("InvisibleFrame.MaxConversionsPerSplash");
|
||||
invisibleFrameGlowEmptyFrames = config.getConfiguration().getBoolean("InvisibleFrame.GlowEmptyFrames");
|
||||
|
||||
storageType = KeyUtils.imageFrameKey(config.getConfiguration().getString("Storage.Type"));
|
||||
storageOptions = config.getConfiguration().getConfigurationSection("Storage.Options").getValues(true).entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().toString()));
|
||||
|
||||
debugLogging = config.getConfiguration().getBoolean("DebugLogging");
|
||||
|
||||
if (updaterTask != null) {
|
||||
updaterTask.cancel();
|
||||
}
|
||||
updaterEnabled = config.getConfiguration().getBoolean("Updater");
|
||||
if (updaterEnabled) {
|
||||
Bukkit.getPluginManager().registerEvents(new Updater(), this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.api.events;
|
||||
|
||||
import com.loohp.imageframe.objectholders.ImageMap;
|
||||
import com.loohp.platformscheduler.Scheduler;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
import static com.loohp.imageframe.utils.ImageUtils.copy;
|
||||
|
||||
public class HDMapPreRespondEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
private final Player player;
|
||||
private final int mapId;
|
||||
private final ImageMap imageMap;
|
||||
private final MapView mapView;
|
||||
|
||||
private boolean requestAccepted;
|
||||
private BufferedImage image;
|
||||
|
||||
public HDMapPreRespondEvent(Player player, int mapId, ImageMap imageMap, MapView mapView, boolean requestAccepted, BufferedImage image) {
|
||||
super(!Scheduler.isPrimaryThread());
|
||||
this.player = player;
|
||||
this.mapId = mapId;
|
||||
this.imageMap = imageMap;
|
||||
this.mapView = mapView;
|
||||
this.requestAccepted = requestAccepted;
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
public Player getPlayer() {
|
||||
return player;
|
||||
}
|
||||
|
||||
public int getMapId() {
|
||||
return mapId;
|
||||
}
|
||||
|
||||
public ImageMap getImageMap() {
|
||||
return imageMap;
|
||||
}
|
||||
|
||||
public MapView getMapView() {
|
||||
return mapView;
|
||||
}
|
||||
|
||||
public boolean isRequestAccepted() {
|
||||
return requestAccepted;
|
||||
}
|
||||
|
||||
public void setRequestAccepted(boolean requestAccepted) {
|
||||
this.requestAccepted = requestAccepted;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return image == null ? null : copy(image);
|
||||
}
|
||||
|
||||
public void setImage(BufferedImage image) {
|
||||
this.image = image == null ? null : copy(image);
|
||||
}
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.api.events;
|
||||
|
||||
import com.loohp.imageframe.objectholders.ImageMap;
|
||||
import com.loohp.platformscheduler.Scheduler;
|
||||
import org.bukkit.event.Cancellable;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class ImageMapAddedEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
private final ImageMap imageMap;
|
||||
|
||||
public ImageMapAddedEvent(ImageMap imageMap) {
|
||||
super(!Scheduler.isPrimaryThread());
|
||||
this.imageMap = imageMap;
|
||||
}
|
||||
|
||||
public ImageMap getImageMap() {
|
||||
return imageMap;
|
||||
}
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.api.events;
|
||||
|
||||
import com.loohp.imageframe.objectholders.ImageMap;
|
||||
import com.loohp.platformscheduler.Scheduler;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class ImageMapDeletedEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
private final ImageMap imageMap;
|
||||
|
||||
public ImageMapDeletedEvent(ImageMap imageMap) {
|
||||
super(!Scheduler.isPrimaryThread());
|
||||
this.imageMap = imageMap;
|
||||
}
|
||||
|
||||
public ImageMap getImageMap() {
|
||||
return imageMap;
|
||||
}
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.api.events;
|
||||
|
||||
import com.loohp.imageframe.objectholders.ImageMap;
|
||||
import com.loohp.platformscheduler.Scheduler;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.HandlerList;
|
||||
|
||||
public class ImageMapUpdatedEvent extends Event {
|
||||
|
||||
private static final HandlerList HANDLERS = new HandlerList();
|
||||
|
||||
public static HandlerList getHandlerList() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
private final ImageMap imageMap;
|
||||
|
||||
public ImageMapUpdatedEvent(ImageMap imageMap) {
|
||||
super(!Scheduler.isPrimaryThread());
|
||||
this.imageMap = imageMap;
|
||||
}
|
||||
|
||||
public ImageMap getImageMap() {
|
||||
return imageMap;
|
||||
}
|
||||
|
||||
public HandlerList getHandlers() {
|
||||
return HANDLERS;
|
||||
}
|
||||
|
||||
}
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2022. Contributors
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
+10
-3
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of InteractiveChatDiscordSrvAddon.
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2022. Contributors
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -21,6 +21,7 @@
|
||||
package com.loohp.imageframe.debug;
|
||||
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
@@ -28,6 +29,12 @@ import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
public class Debug implements Listener {
|
||||
|
||||
public static void debug(String message) {
|
||||
if (ImageFrame.debugLogging) {
|
||||
Bukkit.getConsoleSender().sendMessage("[IMAGEFRAME|DEBUG] " + message);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoinPluginActive(PlayerJoinEvent event) {
|
||||
if (event.getPlayer().getName().equals("LOOHP") || event.getPlayer().getName().equals("AppLEshakE")) {
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.hooks.bedrock;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class BedrockHook {
|
||||
|
||||
private static BedrockHookPlatform PLATFORM;
|
||||
|
||||
public static void setBedrockHookPlatform(BedrockHookPlatform bedrockHookPlatform) {
|
||||
PLATFORM = bedrockHookPlatform;
|
||||
}
|
||||
|
||||
public static boolean isBedrockPlayer(UUID uuid) {
|
||||
return PLATFORM.isBedrockPlayer(uuid);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.hooks.bedrock;
|
||||
|
||||
import java.util.UUID;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
|
||||
public interface BedrockHookPlatform {
|
||||
|
||||
boolean isBedrockPlayer(UUID uuid);
|
||||
|
||||
boolean sendForm(UUID uuid, FormBuilder<?, ?, ?> formBuilder);
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.hooks.bedrock.floodgate;
|
||||
|
||||
import com.loohp.imageframe.hooks.bedrock.BedrockHookPlatform;
|
||||
import java.util.UUID;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
import org.geysermc.floodgate.api.FloodgateApi;
|
||||
|
||||
public class FloodgateHookPlatform implements BedrockHookPlatform {
|
||||
|
||||
@Override
|
||||
public boolean isBedrockPlayer(UUID uuid) {
|
||||
return FloodgateApi.getInstance().isFloodgatePlayer(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendForm(UUID uuid, FormBuilder<?, ?, ?> formBuilder) {
|
||||
return FloodgateApi.getInstance().sendForm(uuid, formBuilder);
|
||||
}
|
||||
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.hooks.bedrock.geyser;
|
||||
|
||||
import com.loohp.imageframe.hooks.bedrock.BedrockHookPlatform;
|
||||
import java.util.UUID;
|
||||
import org.geysermc.cumulus.form.util.FormBuilder;
|
||||
import org.geysermc.geyser.api.GeyserApi;
|
||||
|
||||
public class GeyserHookPlatform implements BedrockHookPlatform {
|
||||
|
||||
@Override
|
||||
public boolean isBedrockPlayer(UUID uuid) {
|
||||
return GeyserApi.api().isBedrockPlayer(uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendForm(UUID uuid, FormBuilder<?, ?, ?> formBuilder) {
|
||||
return GeyserApi.api().sendForm(uuid, formBuilder);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.hooks.viaversion;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class ViaHook {
|
||||
|
||||
public static final int MAP_ID_EXPANSION_VERSION = 346;
|
||||
|
||||
public static boolean isPlayerLegacy(Player player) {
|
||||
int version = Via.getAPI().getPlayerVersion(player.getUniqueId());
|
||||
return version >= 0 && version < MAP_ID_EXPANSION_VERSION;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,421 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.invisibleframe;
|
||||
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import com.loohp.imageframe.nms.NMS;
|
||||
import com.loohp.imageframe.utils.MCVersion;
|
||||
import com.loohp.platformscheduler.ScheduledTask;
|
||||
import com.loohp.platformscheduler.Scheduler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.NamespacedKey;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.SoundCategory;
|
||||
import org.bukkit.entity.AreaEffectCloud;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.ItemFrame;
|
||||
import org.bukkit.entity.ThrownPotion;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.entity.AreaEffectCloudApplyEvent;
|
||||
import org.bukkit.event.entity.EntityDamageByEntityEvent;
|
||||
import org.bukkit.event.entity.EntitySpawnEvent;
|
||||
import org.bukkit.event.entity.ItemSpawnEvent;
|
||||
import org.bukkit.event.entity.LingeringPotionSplashEvent;
|
||||
import org.bukkit.event.entity.PotionSplashEvent;
|
||||
import org.bukkit.event.hanging.HangingBreakEvent;
|
||||
import org.bukkit.event.hanging.HangingPlaceEvent;
|
||||
import org.bukkit.event.inventory.PrepareGrindstoneEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.inventory.CraftingRecipe;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.RecipeChoice;
|
||||
import org.bukkit.inventory.ShapelessRecipe;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.persistence.PersistentDataType;
|
||||
import org.bukkit.potion.PotionEffectType;
|
||||
import org.bukkit.potion.PotionType;
|
||||
import org.bukkit.util.BoundingBox;
|
||||
import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class InvisibleFrameManager implements Listener {
|
||||
|
||||
public static final NamespacedKey INVISIBLE_GLOW_ITEM_FRAME_CRAFTING_KEY = new NamespacedKey(ImageFrame.plugin, "invisible_glow_item_frame");
|
||||
public static final NamespacedKey INVISIBLE_KEY = new NamespacedKey(ImageFrame.plugin, "invisible");
|
||||
|
||||
private final Map<UUID, Location> removedInvisibleFrames;
|
||||
private final Set<UUID> knownAreaEffectClouds;
|
||||
|
||||
private final AtomicLong itemFramesMadeInvisible;
|
||||
private final AtomicLong invisibleItemFramesPlaced;
|
||||
|
||||
public InvisibleFrameManager() {
|
||||
this.removedInvisibleFrames = new ConcurrentHashMap<>();
|
||||
this.knownAreaEffectClouds = ConcurrentHashMap.newKeySet();
|
||||
|
||||
this.itemFramesMadeInvisible = new AtomicLong(0);
|
||||
this.invisibleItemFramesPlaced = new AtomicLong(0);
|
||||
|
||||
Bukkit.getPluginManager().registerEvents(this, ImageFrame.plugin);
|
||||
|
||||
if (ImageFrame.version.isNewerOrEqualTo(MCVersion.V1_17)) {
|
||||
addRecipes();
|
||||
}
|
||||
}
|
||||
|
||||
public AtomicLong getItemFramesMadeInvisible() {
|
||||
return itemFramesMadeInvisible;
|
||||
}
|
||||
|
||||
public AtomicLong getInvisibleItemFramesPlaced() {
|
||||
return invisibleItemFramesPlaced;
|
||||
}
|
||||
|
||||
private void addRecipes() {
|
||||
ItemStack result = new ItemStack(Material.valueOf("GLOW_ITEM_FRAME"));
|
||||
ShapelessRecipe recipe = new ShapelessRecipe(INVISIBLE_GLOW_ITEM_FRAME_CRAFTING_KEY, withInvisibleItemFrameData(result))
|
||||
.addIngredient(Material.valueOf("GLOW_INK_SAC"))
|
||||
.addIngredient(new RecipeChoice.ExactChoice(withInvisibleItemFrameData(new ItemStack(Material.ITEM_FRAME))));
|
||||
if (ImageFrame.version.isNewerOrEqualTo(MCVersion.V1_20)) {
|
||||
CraftingRecipe vanillaRecipe = (CraftingRecipe) Bukkit.getRecipesFor(result).stream()
|
||||
.filter(r -> r instanceof CraftingRecipe)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (vanillaRecipe != null) {
|
||||
recipe.setCategory(vanillaRecipe.getCategory());
|
||||
recipe.setGroup(vanillaRecipe.getGroup());
|
||||
}
|
||||
}
|
||||
Bukkit.addRecipe(recipe);
|
||||
}
|
||||
|
||||
public ItemStack withInvisibleItemFrameData(ItemStack itemStack) {
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
return itemStack;
|
||||
}
|
||||
if (isInvisibleItemFrame(itemStack)) {
|
||||
return itemStack;
|
||||
}
|
||||
ItemStack modified = NMS.getInstance().withInvisibleItemFrameMeta(itemStack);
|
||||
ItemMeta meta = modified.getItemMeta();
|
||||
if (meta == null) {
|
||||
return itemStack;
|
||||
}
|
||||
meta.getPersistentDataContainer().set(INVISIBLE_KEY, PersistentDataType.BYTE, (byte) 1);
|
||||
modified.setItemMeta(meta);
|
||||
return modified;
|
||||
}
|
||||
|
||||
public boolean isInvisibleItemFrame(ItemStack itemStack) {
|
||||
if (itemStack == null || !itemStack.hasItemMeta()) {
|
||||
return false;
|
||||
}
|
||||
ItemMeta meta = itemStack.getItemMeta();
|
||||
if (meta == null) {
|
||||
return false;
|
||||
}
|
||||
return meta.getPersistentDataContainer().getOrDefault(INVISIBLE_KEY, PersistentDataType.BYTE, (byte) 0) > 0;
|
||||
}
|
||||
|
||||
public void setInvisibleItemFrameData(Entity entity) {
|
||||
if (entity instanceof ItemFrame) {
|
||||
entity.getPersistentDataContainer().set(INVISIBLE_KEY, PersistentDataType.BYTE, (byte) 1);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isInvisibleItemFrame(Entity entity) {
|
||||
return entity instanceof ItemFrame && entity.getPersistentDataContainer().getOrDefault(INVISIBLE_KEY, PersistentDataType.BYTE, (byte) 0) > 0;
|
||||
}
|
||||
|
||||
@SuppressWarnings("ConstantValue")
|
||||
public void updateInvisibleItemFrame(ItemFrame itemFrame) {
|
||||
if (!itemFrame.isValid()) {
|
||||
return;
|
||||
}
|
||||
ItemStack itemStack = itemFrame.getItem();
|
||||
if (itemStack == null || itemStack.getType().equals(Material.AIR)) {
|
||||
if (ImageFrame.invisibleFrameGlowEmptyFrames) {
|
||||
itemFrame.setGlowing(true);
|
||||
}
|
||||
itemFrame.setVisible(true);
|
||||
} else {
|
||||
itemFrame.setGlowing(false);
|
||||
itemFrame.setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onGrindstone(PrepareGrindstoneEvent event) {
|
||||
Inventory inventory = event.getInventory();
|
||||
if (isInvisibleItemFrame(inventory.getItem(0)) || isInvisibleItemFrame(inventory.getItem(1))) {
|
||||
event.setResult(null);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlace(HangingPlaceEvent event) {
|
||||
if (!isInvisibleItemFrame(event.getItemStack())) {
|
||||
return;
|
||||
}
|
||||
Entity entity = event.getEntity();
|
||||
Location location = entity.getLocation();
|
||||
setInvisibleItemFrameData(entity);
|
||||
invisibleItemFramesPlaced.incrementAndGet();
|
||||
Scheduler.runTaskLater(ImageFrame.plugin, () -> updateInvisibleItemFrame((ItemFrame) entity), 1, location);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onBreak(HangingBreakEvent event) {
|
||||
Entity entity = event.getEntity();
|
||||
if (!isInvisibleItemFrame(entity)) {
|
||||
return;
|
||||
}
|
||||
UUID uuid = entity.getUniqueId();
|
||||
Location location = entity.getLocation();
|
||||
removedInvisibleFrames.put(uuid, entity.getLocation());
|
||||
Scheduler.runTaskLater(ImageFrame.plugin, () -> removedInvisibleFrames.remove(uuid), 1, location);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onItemSpawn(ItemSpawnEvent event) {
|
||||
Item item = event.getEntity();
|
||||
ItemStack itemStack = item.getItemStack();
|
||||
String type = itemStack.getType().name();
|
||||
if (!type.equals("ITEM_FRAME") && !type.equals("GLOW_ITEM_FRAME")) {
|
||||
return;
|
||||
}
|
||||
if (isInvisibleItemFrame(itemStack)) {
|
||||
return;
|
||||
}
|
||||
for (Iterator<Map.Entry<UUID, Location>> itr = removedInvisibleFrames.entrySet().iterator(); itr.hasNext();) {
|
||||
Map.Entry<UUID, Location> entry = itr.next();
|
||||
Location location = entry.getValue();
|
||||
if (location.getWorld().equals(item.getWorld())) {
|
||||
BoundingBox boundingBox = BoundingBox.of(location, 1, 1, 1);
|
||||
if (boundingBox.contains(item.getLocation().toVector())) {
|
||||
ItemStack modified = withInvisibleItemFrameData(itemStack);
|
||||
item.setItemStack(modified);
|
||||
itr.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPlaceItem(PlayerInteractEntityEvent event) {
|
||||
Entity entity = event.getRightClicked();
|
||||
if (!isInvisibleItemFrame(entity)) {
|
||||
return;
|
||||
}
|
||||
Location location = entity.getLocation();
|
||||
Scheduler.runTaskLater(ImageFrame.plugin, () -> updateInvisibleItemFrame((ItemFrame) entity), 1, location);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onRemoveItem(EntityDamageByEntityEvent event) {
|
||||
Entity entity = event.getEntity();
|
||||
if (!isInvisibleItemFrame(entity)) {
|
||||
return;
|
||||
}
|
||||
Location location = entity.getLocation();
|
||||
Scheduler.runTaskLater(ImageFrame.plugin, () -> updateInvisibleItemFrame((ItemFrame) entity), 1, location);
|
||||
}
|
||||
|
||||
public ItemStack splitItemStacks(ItemStack itemStack, int maxSize) {
|
||||
if (maxSize < 0) {
|
||||
return null;
|
||||
}
|
||||
int amount = itemStack.getAmount();
|
||||
if (amount <= maxSize) {
|
||||
return null;
|
||||
}
|
||||
itemStack.setAmount(maxSize);
|
||||
ItemStack splitStack = itemStack.clone();
|
||||
splitStack.setAmount(amount - maxSize);
|
||||
return splitStack;
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPotionSplash(PotionSplashEvent event) {
|
||||
ThrownPotion thrownPotion = event.getEntity();
|
||||
if (thrownPotion.getEffects().stream().noneMatch(e -> e.getType().equals(PotionEffectType.INVISIBILITY))) {
|
||||
return;
|
||||
}
|
||||
int conversionsRemaining = ImageFrame.invisibleFrameMaxConversionsPerSplash;
|
||||
BoundingBox boundingBox = thrownPotion.getBoundingBox().expand(4.0, 2.0, 4.0);
|
||||
for (Entity entity : thrownPotion.getWorld().getNearbyEntities(boundingBox)) {
|
||||
if (conversionsRemaining == 0) {
|
||||
break;
|
||||
}
|
||||
if (!(entity instanceof Item)) {
|
||||
continue;
|
||||
}
|
||||
Item item = (Item) entity;
|
||||
ItemStack itemStack = item.getItemStack();
|
||||
String type = itemStack.getType().name();
|
||||
if (!type.equals("ITEM_FRAME") && !type.equals("GLOW_ITEM_FRAME")) {
|
||||
continue;
|
||||
}
|
||||
if (isInvisibleItemFrame(itemStack)) {
|
||||
continue;
|
||||
}
|
||||
if (entity.getLocation().distanceSquared(thrownPotion.getLocation()) < 16.0) {
|
||||
ItemStack splitStack = splitItemStacks(itemStack, conversionsRemaining);
|
||||
Location location = item.getLocation();
|
||||
if (itemStack.getAmount() > 0) {
|
||||
itemFramesMadeInvisible.addAndGet(itemStack.getAmount());
|
||||
item.setItemStack(withInvisibleItemFrameData(itemStack));
|
||||
item.getWorld().playSound(item.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, SoundCategory.BLOCKS, 1F, 1F);
|
||||
} else {
|
||||
item.remove();
|
||||
}
|
||||
Vector velocity = null;
|
||||
if (splitStack != null) {
|
||||
Item splitItem = thrownPotion.getWorld().dropItem(location, splitStack);
|
||||
velocity = splitItem.getVelocity();
|
||||
splitItem.setVelocity(new Vector(0, 0, 0));
|
||||
}
|
||||
if (velocity != null) {
|
||||
item.setVelocity(velocity);
|
||||
}
|
||||
if (conversionsRemaining >= 0) {
|
||||
conversionsRemaining = Math.max(0, conversionsRemaining - itemStack.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasInvisibilityEffect(AreaEffectCloud areaEffectCloud) {
|
||||
PotionType baseType = areaEffectCloud.getBasePotionType();
|
||||
if (baseType != null) {
|
||||
if (baseType.getPotionEffects().stream().anyMatch(e -> e.getType().equals(PotionEffectType.INVISIBILITY))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return areaEffectCloud.hasCustomEffect(PotionEffectType.INVISIBILITY);
|
||||
}
|
||||
|
||||
private void handleAreaEffectCloud(AreaEffectCloud areaEffectCloud) {
|
||||
UUID uuid = areaEffectCloud.getUniqueId();
|
||||
if (!knownAreaEffectClouds.add(uuid)) {
|
||||
return;
|
||||
}
|
||||
AtomicReference<Runnable> runnableReference = new AtomicReference<>(null);
|
||||
AtomicReference<ScheduledTask> taskReference = new AtomicReference<>(null);
|
||||
runnableReference.set(new Runnable() {
|
||||
int conversionsRemaining = ImageFrame.invisibleFrameMaxConversionsPerSplash;
|
||||
boolean firstTick = true;
|
||||
@Override
|
||||
public void run() {
|
||||
if (firstTick) {
|
||||
firstTick = false;
|
||||
} else if (!areaEffectCloud.isValid()) {
|
||||
ScheduledTask task = taskReference.get();
|
||||
if (task != null) {
|
||||
task.cancel();
|
||||
}
|
||||
knownAreaEffectClouds.remove(uuid);
|
||||
return;
|
||||
}
|
||||
taskReference.set(Scheduler.runTaskLater(ImageFrame.plugin, runnableReference.get(), 1, areaEffectCloud.getLocation()));
|
||||
if (!hasInvisibilityEffect(areaEffectCloud)) {
|
||||
return;
|
||||
}
|
||||
if (areaEffectCloud.getTicksLived() % 5 != 0) {
|
||||
return;
|
||||
}
|
||||
BoundingBox boundingBox = areaEffectCloud.getBoundingBox();
|
||||
for (Entity entity : areaEffectCloud.getWorld().getNearbyEntities(boundingBox)) {
|
||||
if (conversionsRemaining == 0) {
|
||||
break;
|
||||
}
|
||||
if (!(entity instanceof Item)) {
|
||||
continue;
|
||||
}
|
||||
Item item = (Item) entity;
|
||||
ItemStack itemStack = item.getItemStack();
|
||||
String type = itemStack.getType().name();
|
||||
if (!type.equals("ITEM_FRAME") && !type.equals("GLOW_ITEM_FRAME")) {
|
||||
continue;
|
||||
}
|
||||
if (isInvisibleItemFrame(itemStack)) {
|
||||
continue;
|
||||
}
|
||||
ItemStack splitStack = splitItemStacks(itemStack, conversionsRemaining);
|
||||
Location location = item.getLocation();
|
||||
if (itemStack.getAmount() > 0) {
|
||||
itemFramesMadeInvisible.addAndGet(itemStack.getAmount());
|
||||
item.setItemStack(withInvisibleItemFrameData(itemStack));
|
||||
item.getWorld().playSound(item.getLocation(), Sound.BLOCK_ENCHANTMENT_TABLE_USE, SoundCategory.BLOCKS, 1F, 1F);
|
||||
} else {
|
||||
item.remove();
|
||||
}
|
||||
Vector velocity = null;
|
||||
if (splitStack != null) {
|
||||
Item splitItem = areaEffectCloud.getWorld().dropItem(location, splitStack);
|
||||
velocity = splitItem.getVelocity();
|
||||
splitItem.setVelocity(new Vector(0, 0, 0));
|
||||
}
|
||||
if (velocity != null) {
|
||||
item.setVelocity(velocity);
|
||||
}
|
||||
if (conversionsRemaining >= 0) {
|
||||
conversionsRemaining = Math.max(0, conversionsRemaining - itemStack.getAmount());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
runnableReference.get().run();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPotionLinger(EntitySpawnEvent event) {
|
||||
Entity entity = event.getEntity();
|
||||
if (entity instanceof AreaEffectCloud) {
|
||||
handleAreaEffectCloud((AreaEffectCloud) entity);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onPotionLinger(LingeringPotionSplashEvent event) {
|
||||
handleAreaEffectCloud(event.getAreaEffectCloud());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onAreaEffectCloudApply(AreaEffectCloudApplyEvent event) {
|
||||
handleAreaEffectCloud(event.getEntity());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.language;
|
||||
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import com.loohp.imageframe.debug.Debug;
|
||||
import com.loohp.imageframe.utils.HTTPRequestUtils;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.TranslationArgument;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class LanguageManager {
|
||||
|
||||
public static final String LANGUAGE_META_URL = "https://api.loohpjames.com/spigot/plugins/imageframe/language";
|
||||
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().serializeNulls().create();
|
||||
private static final Pattern FORMAT_PATTERN = Pattern.compile("%(?:(\\d+)\\$)?s");
|
||||
|
||||
private final File languageFolder;
|
||||
private final Map<String, Map<String, String>> translations;
|
||||
|
||||
public LanguageManager() {
|
||||
this.languageFolder = new File(ImageFrame.plugin.getDataFolder(), "language");
|
||||
this.languageFolder.mkdirs();
|
||||
this.translations = new ConcurrentHashMap<>();
|
||||
|
||||
reloadLanguages();
|
||||
}
|
||||
|
||||
public void reloadLanguages() {
|
||||
downloadTranslations();
|
||||
this.translations.clear();
|
||||
loadTranslations();
|
||||
}
|
||||
|
||||
public Set<String> getLoadedLanguages() {
|
||||
return Collections.unmodifiableSet(translations.keySet());
|
||||
}
|
||||
|
||||
public void downloadTranslations() {
|
||||
try {
|
||||
File languageHashFile = new File(ImageFrame.plugin.getDataFolder(), "language_hashes.json");
|
||||
JsonObject languageHashes;
|
||||
if (languageHashFile.exists()) {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(languageHashFile.toPath()), StandardCharsets.UTF_8))) {
|
||||
languageHashes = GSON.fromJson(reader, JsonObject.class);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
languageHashes = new JsonObject();
|
||||
}
|
||||
} else {
|
||||
languageHashes = new JsonObject();
|
||||
}
|
||||
|
||||
JsonObject json;
|
||||
try {
|
||||
Debug.debug("Downloading language meta...");
|
||||
json = HTTPRequestUtils.getJsonResponse(LANGUAGE_META_URL);
|
||||
} catch (Exception e) {
|
||||
throw new IOException("Unable to fetch language meta from \"api.loohpjames.com\". This could be an internet issue or \"api.loohpjames.com\" is down. If the plugin functions correctly after this, this error can be ignored.", e);
|
||||
}
|
||||
for (JsonElement element : json.get("languages").getAsJsonArray()) {
|
||||
JsonObject languageObj = element.getAsJsonObject();
|
||||
String language = languageObj.get("language").getAsString();
|
||||
String url = languageObj.get("url").getAsString();
|
||||
String hash = languageObj.get("hash").getAsString();
|
||||
|
||||
String existingHash = languageHashes.has(language) ? languageHashes.get(language).getAsString() : null;
|
||||
if (!hash.equalsIgnoreCase(existingHash)) {
|
||||
Debug.debug("Downloading language " + language + "...");
|
||||
try (
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(HTTPRequestUtils.getInputStream(url), StandardCharsets.UTF_8));
|
||||
PrintWriter writer = new PrintWriter(new OutputStreamWriter(Files.newOutputStream(new File(languageFolder, language + ".json").toPath()), StandardCharsets.UTF_8));
|
||||
) {
|
||||
reader.lines().forEachOrdered(l -> writer.println(l));
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
new IOException("Unable to download language " + language + " from " + url, e).printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
languageHashes.addProperty(language, hash);
|
||||
}
|
||||
|
||||
try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(Files.newOutputStream(languageHashFile.toPath()), StandardCharsets.UTF_8))) {
|
||||
writer.println(GSON.toJson(languageHashes));
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
new IOException("Unable to save language hashes to " + languageHashFile.getAbsolutePath(), e).printStackTrace();
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void loadTranslations() {
|
||||
for (File file : languageFolder.listFiles()) {
|
||||
String name = file.getName();
|
||||
if (name.endsWith(".json")) {
|
||||
String language = name.substring(0, name.indexOf("."));
|
||||
Map<String, String> translations = new ConcurrentHashMap<>();
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(Files.newInputStream(file.toPath()), StandardCharsets.UTF_8))) {
|
||||
JsonObject jsonObject = GSON.fromJson(reader, JsonObject.class);
|
||||
for (Map.Entry<String, JsonElement> entry : jsonObject.entrySet()) {
|
||||
translations.put(entry.getKey(), entry.getValue().getAsString());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
new IOException("Unable to load language file " + file.getAbsolutePath(), e).printStackTrace();
|
||||
}
|
||||
this.translations.put(language, translations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getTranslationKeys(String language) {
|
||||
return Sets.union(translations.getOrDefault(language, Collections.emptyMap()).keySet(), translations.get(ImageFrame.language).keySet());
|
||||
}
|
||||
|
||||
public String getTranslation(String key, String language) {
|
||||
String translation = translations.getOrDefault(language.toLowerCase(), Collections.emptyMap()).get(key);
|
||||
if (translation != null) {
|
||||
return translation;
|
||||
}
|
||||
if (ImageFrame.language.equalsIgnoreCase(language)) {
|
||||
return null;
|
||||
}
|
||||
return getTranslation(key, ImageFrame.language);
|
||||
}
|
||||
|
||||
public List<String> getListTranslation(String key, String language) {
|
||||
return getListTranslation(key, language, Byte.MAX_VALUE);
|
||||
}
|
||||
|
||||
public List<String> getListTranslation(String key, String language, int maxSize) {
|
||||
List<String> result = new ArrayList<>();
|
||||
for (int i = 0; i < maxSize; i++) {
|
||||
String translated = getTranslation(key + "." + i, language);
|
||||
if (translated == null) {
|
||||
return result;
|
||||
}
|
||||
result.add(translated);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public Component resolve(Component component, String language) {
|
||||
if (component instanceof TranslatableComponent) {
|
||||
component = convertSingleTranslatable((TranslatableComponent) component, language);
|
||||
}
|
||||
List<Component> children = new ArrayList<>(component.children());
|
||||
children.replaceAll(child -> resolve(child, language));
|
||||
return component.children(children);
|
||||
}
|
||||
|
||||
private Component convertSingleTranslatable(TranslatableComponent component, String language) {
|
||||
String translation = getTranslation(component.key(), language);
|
||||
if (translation == null) {
|
||||
return component;
|
||||
}
|
||||
|
||||
List<TranslationArgument> arguments = component.arguments();
|
||||
Matcher matcher = FORMAT_PATTERN.matcher(translation);
|
||||
|
||||
int lastEnd = 0;
|
||||
int nextUnindexed = 0;
|
||||
List<Component> parts = new ArrayList<>();
|
||||
|
||||
while (matcher.find()) {
|
||||
if (matcher.start() > lastEnd) {
|
||||
String literal = translation.substring(lastEnd, matcher.start());
|
||||
if (!literal.isEmpty()) {
|
||||
parts.add(Component.text(literal));
|
||||
}
|
||||
}
|
||||
String indexGroup = matcher.group(1);
|
||||
int argIndex;
|
||||
if (indexGroup != null) {
|
||||
argIndex = Integer.parseInt(indexGroup) - 1;
|
||||
} else {
|
||||
argIndex = nextUnindexed++;
|
||||
}
|
||||
if (argIndex >= 0 && argIndex < arguments.size()) {
|
||||
parts.add(arguments.get(argIndex).asComponent());
|
||||
} else {
|
||||
parts.add(Component.text(matcher.group()));
|
||||
}
|
||||
lastEnd = matcher.end();
|
||||
}
|
||||
if (lastEnd < translation.length()) {
|
||||
String tail = translation.substring(lastEnd);
|
||||
if (!tail.isEmpty()) {
|
||||
parts.add(Component.text(tail));
|
||||
}
|
||||
}
|
||||
Component result = Component.empty().style(component.style());
|
||||
for (Component part : parts) {
|
||||
result = result.append(part);
|
||||
}
|
||||
for (Component child : component.children()) {
|
||||
result = result.append(child);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.language;
|
||||
|
||||
import com.loohp.imageframe.objectholders.IFPlayerPreference;
|
||||
import com.loohp.imageframe.objectholders.ImageMapAccessPermissionType;
|
||||
import com.loohp.imageframe.objectholders.PreferenceState;
|
||||
|
||||
public class TranslationKey {
|
||||
|
||||
public static final String RELOADED = "imageframe.messages.reloaded";
|
||||
public static final String RESYNC = "imageframe.messages.resync";
|
||||
public static final String STORAGE_MIGRATION = "imageframe.messages.storage_migration";
|
||||
public static final String IMAGE_MAP_PROCESSING = "imageframe.messages.image_map_processing";
|
||||
public static final String IMAGE_MAP_PROCESSING_ACTION_BAR = "imageframe.messages.image_map_processing_action_bar";
|
||||
public static final String IMAGE_MAP_QUEUED_ACTION_BAR = "imageframe.messages.image_map_queued_action_bar";
|
||||
public static final String IMAGE_MAP_CREATED = "imageframe.messages.image_map_created";
|
||||
public static final String IMAGE_MAP_REFRESHED = "imageframe.messages.image_map_refreshed";
|
||||
public static final String IMAGE_MAP_DELETED = "imageframe.messages.image_map_deleted";
|
||||
public static final String IMAGE_MAP_RENAMED = "imageframe.messages.image_map_renamed";
|
||||
public static final String IMAGE_MAP_TOGGLE_PAUSED = "imageframe.messages.image_map_toggle_paused";
|
||||
public static final String IMAGE_MAP_PLAYBACK_JUMP_TO = "imageframe.messages.image_map_playback_jump_to";
|
||||
public static final String IMAGE_MAP_PLAYER_PURGE = "imageframe.messages.image_map_player_purge";
|
||||
public static final String SET_CREATOR = "imageframe.messages.set_creator";
|
||||
public static final String INVALID_OVERLAY_MAP = "imageframe.messages.invalid_overlay_map";
|
||||
public static final String IMAGE_MAP_ALREADY_QUEUED = "imageframe.messages.image_map_already_queued";
|
||||
public static final String UNABLE_TO_LOAD_MAP = "imageframe.messages.unable_to_load_map";
|
||||
public static final String UNABLE_TO_CHANGE_IMAGE_TYPE = "imageframe.messages.unable_to_change_image_type";
|
||||
public static final String UNKNOWN_ERROR = "imageframe.messages.unknown_error";
|
||||
public static final String IMAGE_OVER_MAX_FILE_SIZE = "imageframe.messages.image_over_max_file_size";
|
||||
public static final String NOT_AN_IMAGE_MAP = "imageframe.messages.not_an_image_map";
|
||||
public static final String UPLOAD_LINK = "imageframe.messages.upload_link";
|
||||
public static final String UPLOAD_EXPIRED = "imageframe.messages.upload_expired";
|
||||
|
||||
public static final String URL_IMAGE_MAP_INFO_1 = "imageframe.messages.url_image_map_info.1";
|
||||
public static final String URL_IMAGE_MAP_INFO_2 = "imageframe.messages.url_image_map_info.2";
|
||||
public static final String URL_IMAGE_MAP_INFO_3 = "imageframe.messages.url_image_map_info.3";
|
||||
public static final String URL_IMAGE_MAP_INFO_4 = "imageframe.messages.url_image_map_info.4";
|
||||
public static final String URL_IMAGE_MAP_INFO_5 = "imageframe.messages.url_image_map_info.5";
|
||||
public static final String URL_IMAGE_MAP_INFO_6 = "imageframe.messages.url_image_map_info.6";
|
||||
public static final String URL_IMAGE_MAP_INFO_7 = "imageframe.messages.url_image_map_info.7";
|
||||
public static final String URL_IMAGE_MAP_INFO_8 = "imageframe.messages.url_image_map_info.8";
|
||||
public static final String URL_IMAGE_MAP_INFO_9 = "imageframe.messages.url_image_map_info.9";
|
||||
|
||||
public static final String NO_PERMISSION = "imageframe.messages.no_permission";
|
||||
public static final String NO_CONSOLE = "imageframe.messages.no_console";
|
||||
public static final String PLAYER_NOT_FOUND = "imageframe.messages.player_not_found";
|
||||
public static final String INVALID_USAGE = "imageframe.messages.invalid_usage";
|
||||
public static final String NOT_ENOUGH_MAPS = "imageframe.messages.not_enough_maps";
|
||||
public static final String OVERSIZE = "imageframe.messages.oversize";
|
||||
public static final String URL_RESTRICTED = "imageframe.messages.url_restricted";
|
||||
public static final String PLAYER_CREATION_LIMIT_REACHED = "imageframe.messages.player_creation_limit_reached";
|
||||
public static final String DUPLICATE_MAP_NAME = "imageframe.messages.duplicate_map_name";
|
||||
public static final String MAP_LOOKUP = "imageframe.messages.map_lookup";
|
||||
public static final String ITEM_FRAME_OCCUPIED = "imageframe.messages.item_frame_occupied";
|
||||
public static final String NOT_ENOUGH_SPACE = "imageframe.messages.not_enough_space";
|
||||
public static final String INVALID_IMAGE_MAP = "imageframe.messages.invalid_image_map";
|
||||
public static final String GIVEN_INVISIBLE_FRAME = "imageframe.messages.given_invisible_frame";
|
||||
|
||||
public static final String ACCESS_UPDATED = "imageframe.messages.access_permission.updated";
|
||||
public static String ACCESS_TYPE(ImageMapAccessPermissionType type) {
|
||||
String key = type == null ? "none" : type.name().toLowerCase();
|
||||
return "imageframe.messages.access_permission.types." + key;
|
||||
}
|
||||
|
||||
public static final String SELECTION_BEGIN = "imageframe.messages.selection.begin";
|
||||
public static final String SELECTION_CLEAR = "imageframe.messages.selection.clear";
|
||||
public static final String SELECTION_CORNER1 = "imageframe.messages.selection.corner1";
|
||||
public static final String SELECTION_CORNER2 = "imageframe.messages.selection.corner2";
|
||||
public static final String SELECTION_INVALID = "imageframe.messages.selection.invalid";
|
||||
public static final String SELECTION_OVERSIZE = "imageframe.messages.selection.oversize";
|
||||
public static final String SELECTION_SUCCESS = "imageframe.messages.selection.success";
|
||||
public static final String SELECTION_NO_SELECTION = "imageframe.messages.selection.no_selection";
|
||||
public static final String SELECTION_INCORRECT_SIZE = "imageframe.messages.selection.incorrect_size";
|
||||
|
||||
public static final String MARKERS_ADD_BEGIN = "imageframe.messages.markers.add_begin";
|
||||
public static final String MARKERS_ADD_CONFIRM = "imageframe.messages.markers.add_confirm";
|
||||
public static final String MARKERS_REMOVE = "imageframe.messages.markers.remove";
|
||||
public static final String MARKERS_CLEAR = "imageframe.messages.markers.clear";
|
||||
public static final String MARKERS_CANCEL = "imageframe.messages.markers.cancel";
|
||||
public static final String MARKERS_DUPLICATE_NAME = "imageframe.messages.markers.duplicate_name";
|
||||
public static final String MARKERS_NOT_A_MARKER = "imageframe.messages.markers.not_a_marker";
|
||||
public static final String MARKERS_NOT_RENDER_WARNING = "imageframe.messages.markers.not_render_on_frame_warning";
|
||||
public static final String MARKERS_LIMIT_REACHED = "imageframe.messages.markers.limit_reached";
|
||||
|
||||
public static final String PREFERENCES_UPDATE = "imageframe.messages.preferences.update_message";
|
||||
public static String PREFERENCES_TYPE(IFPlayerPreference<?> type) {
|
||||
return "imageframe.messages.preferences.keys." + type.name().toLowerCase();
|
||||
}
|
||||
public static String PREFERENCES_VALUE(PreferenceState state) {
|
||||
return "imageframe.messages.preferences.values." + state.name().toLowerCase();
|
||||
}
|
||||
|
||||
public static final String COMBINED_ITEM_NAME = "imageframe.settings.combined_map_item.name";
|
||||
public static final String COMBINED_ITEM_LORE_1 = "imageframe.settings.combined_map_item.lore.1";
|
||||
public static final String COMBINED_ITEM_LORE_2 = "imageframe.settings.combined_map_item.lore.2";
|
||||
public static final String COMBINED_ITEM_LORE_3 = "imageframe.settings.combined_map_item.lore.3";
|
||||
public static final String COMBINED_ITEM_LORE_4 = "imageframe.settings.combined_map_item.lore.4";
|
||||
public static final String COMBINED_ITEM_LORE_5 = "imageframe.settings.combined_map_item.lore.5";
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,335 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.listeners;
|
||||
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import com.loohp.imageframe.utils.MapUtils;
|
||||
import com.loohp.imageframe.utils.ModernEventsUtils;
|
||||
import com.loohp.imageframe.utils.SlotAccessor;
|
||||
import com.loohp.platformscheduler.Scheduler;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Entity;
|
||||
import org.bukkit.entity.Item;
|
||||
import org.bukkit.entity.ItemFrame;
|
||||
import org.bukkit.event.Event;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.EventPriority;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.block.Action;
|
||||
import org.bukkit.event.entity.EntityDamageEvent;
|
||||
import org.bukkit.event.entity.EntityPickupItemEvent;
|
||||
import org.bukkit.event.inventory.ClickType;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.event.inventory.InventoryDragEvent;
|
||||
import org.bukkit.event.inventory.InventoryType;
|
||||
import org.bukkit.event.player.PlayerInteractEntityEvent;
|
||||
import org.bukkit.event.player.PlayerInteractEvent;
|
||||
import org.bukkit.event.player.PlayerItemHeldEvent;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.world.ChunkLoadEvent;
|
||||
import org.bukkit.event.world.EntitiesLoadEvent;
|
||||
import org.bukkit.inventory.EntityEquipment;
|
||||
import org.bukkit.inventory.EquipmentSlot;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.map.MapView;
|
||||
|
||||
import static com.loohp.imageframe.objectholders.CombinedMapItemHandler.containsCombinedMaps;
|
||||
import static com.loohp.imageframe.objectholders.CombinedMapItemHandler.isCombinedMaps;
|
||||
import static com.loohp.imageframe.utils.ImageFilledMapUtils.processImageFilledMap;
|
||||
import static com.loohp.imageframe.utils.ImageFilledMapUtils.processImageFilledMaps;
|
||||
|
||||
public class Events implements Listener {
|
||||
|
||||
@SuppressWarnings("UnstableApiUsage")
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onClick(InventoryClickEvent event) {
|
||||
ItemStack currentItem = event.getCurrentItem();
|
||||
MapView currentMapView = MapUtils.getItemMapView(currentItem);
|
||||
if (currentMapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(currentMapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(currentMapView.getId())) {
|
||||
event.setCurrentItem(new ItemStack(Material.MAP, currentItem.getAmount()));
|
||||
}
|
||||
}
|
||||
ItemStack replacement = processImageFilledMap(event.getCurrentItem());
|
||||
if (replacement != null) {
|
||||
event.setCurrentItem(replacement);
|
||||
}
|
||||
|
||||
boolean isClickingTop = event.getView().getTopInventory().equals(event.getClickedInventory());
|
||||
boolean isClickingBottom = event.getView().getBottomInventory().equals(event.getClickedInventory());
|
||||
|
||||
Inventory inventory = event.getView().getTopInventory();
|
||||
switch (inventory.getType()) {
|
||||
case CRAFTING: {
|
||||
if (event.getClickedInventory() != null && InventoryType.CRAFTING.equals(event.getClickedInventory().getType())) {
|
||||
if (isCombinedMaps(event.getCursor())
|
||||
|| (isClickingBottom && event.isShiftClick() && isCombinedMaps(event.getCurrentItem()))
|
||||
|| (isClickingTop && event.getHotbarButton() != -1 && isCombinedMaps(event.getWhoClicked().getInventory().getItem(event.getHotbarButton())))
|
||||
|| (isClickingTop && event.getClick().equals(ClickType.SWAP_OFFHAND) && isCombinedMaps(event.getWhoClicked().getEquipment().getItemInOffHand()))
|
||||
|| containsCombinedMaps(i -> event.getView().getItem(i), 5)) {
|
||||
event.setResult(Event.Result.DENY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case WORKBENCH: {
|
||||
if ((isClickingTop && isCombinedMaps(event.getCursor()))
|
||||
|| (isClickingBottom && event.isShiftClick() && isCombinedMaps(event.getCurrentItem()))
|
||||
|| (isClickingTop && event.getHotbarButton() != -1 && isCombinedMaps(event.getWhoClicked().getInventory().getItem(event.getHotbarButton())))
|
||||
|| (isClickingTop && event.getClick().equals(ClickType.SWAP_OFFHAND) && isCombinedMaps(event.getWhoClicked().getEquipment().getItemInOffHand()))
|
||||
|| containsCombinedMaps(i -> event.getView().getItem(i), 10)) {
|
||||
event.setResult(Event.Result.DENY);
|
||||
} else if (event.getRawSlot() == 0) {
|
||||
ItemStack map = event.getView().getItem(5);
|
||||
MapView mapView = MapUtils.getItemMapView(map);
|
||||
if (mapView == null) {
|
||||
return;
|
||||
}
|
||||
if (ImageFrame.imageMapManager.getFromMapView(mapView) == null) {
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
for (int i = 1; i <= 9; i++) {
|
||||
if (i == 5) {
|
||||
continue;
|
||||
}
|
||||
ItemStack itemStack = event.getView().getItem(i);
|
||||
if (itemStack != null && itemStack.getType().equals(Material.PAPER)) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
if (count >= 8) {
|
||||
event.setResult(Event.Result.DENY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CRAFTER: {
|
||||
if ((isClickingTop && isCombinedMaps(event.getCursor()))
|
||||
|| (isClickingBottom && event.isShiftClick() && isCombinedMaps(event.getCurrentItem()))
|
||||
|| (isClickingTop && event.getHotbarButton() != -1 && isCombinedMaps(event.getWhoClicked().getInventory().getItem(event.getHotbarButton())))
|
||||
|| (isClickingTop && event.getClick().equals(ClickType.SWAP_OFFHAND) && isCombinedMaps(event.getWhoClicked().getEquipment().getItemInOffHand()))
|
||||
|| containsCombinedMaps(i -> event.getView().getItem(i), 9)) {
|
||||
event.setResult(Event.Result.DENY);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CARTOGRAPHY: {
|
||||
if ((isClickingTop && isCombinedMaps(event.getCursor()))
|
||||
|| (isClickingBottom && event.isShiftClick() && isCombinedMaps(event.getCurrentItem()))
|
||||
|| (isClickingTop && event.getHotbarButton() != -1 && isCombinedMaps(event.getWhoClicked().getInventory().getItem(event.getHotbarButton())))
|
||||
|| (isClickingTop && event.getClick().equals(ClickType.SWAP_OFFHAND) && isCombinedMaps(event.getWhoClicked().getEquipment().getItemInOffHand()))
|
||||
|| containsCombinedMaps(i -> event.getView().getItem(i), 3)) {
|
||||
event.setResult(Event.Result.DENY);
|
||||
} else if (event.getRawSlot() == 2) {
|
||||
ItemStack map = event.getView().getItem(0);
|
||||
MapView mapView = MapUtils.getItemMapView(map);
|
||||
if (mapView == null) {
|
||||
return;
|
||||
}
|
||||
if (ImageFrame.imageMapManager.getFromMapView(mapView) == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack item = event.getView().getItem(1);
|
||||
if (item == null) {
|
||||
return;
|
||||
}
|
||||
if (item.getType().equals(Material.PAPER) || item.getType().equals(Material.GLASS_PANE)) {
|
||||
event.setResult(Event.Result.DENY);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onDrag(InventoryDragEvent event) {
|
||||
Inventory inventory = event.getView().getTopInventory();
|
||||
int size = inventory.getSize();
|
||||
if (inventory.getType().equals(InventoryType.WORKBENCH) || inventory.getType().equals(InventoryType.CARTOGRAPHY)) {
|
||||
if (containsCombinedMaps(event.getNewItems().values()) && event.getNewItems().keySet().stream().anyMatch(i -> i < size)) {
|
||||
event.setResult(Event.Result.DENY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerSwitchSlot(PlayerItemHeldEvent event) {
|
||||
Inventory inventory = event.getPlayer().getInventory();
|
||||
int slot = event.getNewSlot();
|
||||
ItemStack currentItem = inventory.getItem(slot);
|
||||
MapView currentMapView = MapUtils.getItemMapView(currentItem);
|
||||
if (currentMapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(currentMapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(currentMapView.getId())) {
|
||||
inventory.setItem(slot, new ItemStack(Material.MAP, currentItem.getAmount()));
|
||||
}
|
||||
}
|
||||
ItemStack replacement = processImageFilledMap(inventory.getItem(slot));
|
||||
if (replacement != null) {
|
||||
inventory.setItem(slot, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerInteract(PlayerInteractEvent event) {
|
||||
if (event.getAction().equals(Action.PHYSICAL)) {
|
||||
return;
|
||||
}
|
||||
EntityEquipment equipment = event.getPlayer().getEquipment();
|
||||
EquipmentSlot hand = event.getHand();
|
||||
if (hand == null) {
|
||||
return;
|
||||
}
|
||||
ItemStack currentItem = equipment.getItem(hand);
|
||||
MapView currentMapView = MapUtils.getItemMapView(currentItem);
|
||||
if (currentMapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(currentMapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(currentMapView.getId())) {
|
||||
equipment.setItem(hand, new ItemStack(Material.MAP, currentItem.getAmount()));
|
||||
}
|
||||
}
|
||||
ItemStack replacement = processImageFilledMap(equipment.getItem(hand));
|
||||
if (replacement != null) {
|
||||
equipment.setItem(hand, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onPlayerInteractEntity(PlayerInteractEntityEvent event) {
|
||||
EntityEquipment equipment = event.getPlayer().getEquipment();
|
||||
EquipmentSlot hand = event.getHand();
|
||||
ItemStack currentItem = equipment.getItem(hand);
|
||||
MapView currentMapView = MapUtils.getItemMapView(currentItem);
|
||||
if (currentMapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(currentMapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(currentMapView.getId())) {
|
||||
equipment.setItem(hand, new ItemStack(Material.MAP, currentItem.getAmount()));
|
||||
}
|
||||
}
|
||||
ItemStack replacement = processImageFilledMap(equipment.getItem(hand));
|
||||
if (replacement != null) {
|
||||
equipment.setItem(hand, replacement);
|
||||
}
|
||||
|
||||
Entity entity = event.getRightClicked();
|
||||
if (entity instanceof ItemFrame) {
|
||||
ItemFrame itemFrame = (ItemFrame) entity;
|
||||
ItemStack itemStack = itemFrame.getItem();
|
||||
MapView mapView = MapUtils.getItemMapView(itemStack);
|
||||
if (mapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(mapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(mapView.getId())) {
|
||||
itemFrame.setItem(new ItemStack(Material.MAP, itemStack.getAmount()), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onEntityDamage(EntityDamageEvent event) {
|
||||
Entity entity = event.getEntity();
|
||||
if (entity instanceof ItemFrame) {
|
||||
ItemFrame itemFrame = (ItemFrame) entity;
|
||||
ItemStack itemStack = itemFrame.getItem();
|
||||
MapView mapView = MapUtils.getItemMapView(itemStack);
|
||||
if (mapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(mapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(mapView.getId())) {
|
||||
itemFrame.setItem(new ItemStack(Material.MAP, itemStack.getAmount()), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
|
||||
public void onEntityPickupItem(EntityPickupItemEvent event) {
|
||||
Item item = event.getItem();
|
||||
ItemStack currentItem = item.getItemStack();
|
||||
MapView currentMapView = MapUtils.getItemMapView(currentItem);
|
||||
if (currentMapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(currentMapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(currentMapView.getId())) {
|
||||
item.setItemStack(new ItemStack(Material.MAP, currentItem.getAmount()));
|
||||
}
|
||||
}
|
||||
ItemStack replacement = processImageFilledMap(item.getItemStack());
|
||||
if (replacement != null) {
|
||||
item.setItemStack(replacement);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL)
|
||||
public void onChunkLoad(ChunkLoadEvent event) {
|
||||
if (ModernEventsUtils.modernEventsExists()) {
|
||||
return;
|
||||
}
|
||||
Chunk chunk = event.getChunk();
|
||||
Scheduler.runTaskLater(ImageFrame.plugin, () -> {
|
||||
if (!chunk.isLoaded()) {
|
||||
return;
|
||||
}
|
||||
for (Entity entity : chunk.getEntities()) {
|
||||
if (entity instanceof ItemFrame) {
|
||||
ItemFrame itemFrame = (ItemFrame) entity;
|
||||
ItemStack itemStack = itemFrame.getItem();
|
||||
MapView mapView = MapUtils.getItemMapView(itemStack);
|
||||
if (mapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(mapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(mapView.getId())) {
|
||||
Scheduler.runTask(ImageFrame.plugin, () -> itemFrame.setItem(new ItemStack(Material.MAP, itemStack.getAmount()), false), itemFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1, chunk);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Inventory inventory = event.getPlayer().getInventory();
|
||||
processImageFilledMaps(SlotAccessor.of(i -> inventory.getItem(i), (i, s) -> inventory.setItem(i, s)), inventory.getSize());
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerQuit(PlayerQuitEvent event) {
|
||||
Inventory inventory = event.getPlayer().getInventory();
|
||||
processImageFilledMaps(SlotAccessor.of(i -> inventory.getItem(i), (i, s) -> inventory.setItem(i, s)), inventory.getSize());
|
||||
}
|
||||
|
||||
public static class ModernEvents implements Listener {
|
||||
|
||||
@EventHandler(priority = EventPriority.NORMAL)
|
||||
public void onEntityLoad(EntitiesLoadEvent event) {
|
||||
for (Entity entity : event.getEntities()) {
|
||||
if (entity instanceof ItemFrame) {
|
||||
ItemFrame itemFrame = (ItemFrame) entity;
|
||||
ItemStack itemStack = itemFrame.getItem();
|
||||
MapView mapView = MapUtils.getItemMapView(itemStack);
|
||||
if (mapView != null) {
|
||||
if (ImageFrame.imageMapManager.isMapDeleted(mapView) && !ImageFrame.exemptMapIdsFromDeletion.satisfies(mapView.getId())) {
|
||||
Scheduler.runTask(ImageFrame.plugin, () -> itemFrame.setItem(new ItemStack(Material.MAP, itemStack.getAmount()), false), itemFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.media;
|
||||
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import com.loohp.imageframe.utils.GifReader;
|
||||
import com.loohp.imageframe.utils.ImageResolver;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import net.kyori.adventure.key.Key;
|
||||
|
||||
public class GifReaderMediaLoader implements MediaLoader {
|
||||
|
||||
private static final Key IDENTIFIER = Key.key("imageframe", "gif_reader");
|
||||
|
||||
@Override
|
||||
public Key getIdentifier() {
|
||||
return IDENTIFIER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldTryRead(String url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<MediaFrame> tryLoad(String url) throws Exception {
|
||||
List<GifReader.ImageFrame> frames = GifReader.readGif(ImageResolver.getResolvedImageInputStream(url), ImageFrame.maxImageFileSize).get();
|
||||
return frames.stream().map(f -> MediaFrame.animatedFrame(f.getImage(), f.getDelay())).iterator();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.media;
|
||||
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import com.loohp.imageframe.utils.ImageResolver;
|
||||
import net.kyori.adventure.key.Key;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
|
||||
public class ImageIOMediaLoader implements MediaLoader {
|
||||
|
||||
private static final Key IDENTIFIER = Key.key("imageframe", "imageio");
|
||||
|
||||
@Override
|
||||
public Key getIdentifier() {
|
||||
return IDENTIFIER;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldTryRead(String url) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<MediaFrame> tryLoad(String url) throws Exception {
|
||||
BufferedImage image = ImageIO.read(new ByteArrayInputStream(ImageResolver.downloadResolvedImage(url, ImageFrame.maxImageFileSize)));
|
||||
if (image == null) {
|
||||
throw new IOException("ImageIO cannot read media type in " + url);
|
||||
}
|
||||
return MediaLoader.staticImage(image);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.media;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
|
||||
public class MediaFrame {
|
||||
|
||||
public static MediaFrame staticFrame(BufferedImage image) {
|
||||
return new MediaFrame(image, -1);
|
||||
}
|
||||
|
||||
public static MediaFrame animatedFrame(BufferedImage image, int durationMs) {
|
||||
return new MediaFrame(image, durationMs);
|
||||
}
|
||||
|
||||
private final BufferedImage image;
|
||||
private final int durationMs;
|
||||
|
||||
private MediaFrame(BufferedImage image, int durationMs) {
|
||||
this.image = image;
|
||||
this.durationMs = durationMs;
|
||||
}
|
||||
|
||||
public BufferedImage getImage() {
|
||||
return image;
|
||||
}
|
||||
|
||||
public int getDurationMs() {
|
||||
return durationMs;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.media;
|
||||
|
||||
import net.kyori.adventure.key.Key;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
|
||||
public interface MediaLoader {
|
||||
|
||||
static Iterator<MediaFrame> staticImage(BufferedImage image) {
|
||||
return Collections.singletonList(MediaFrame.staticFrame(image)).iterator();
|
||||
}
|
||||
|
||||
Key getIdentifier();
|
||||
|
||||
boolean shouldTryRead(String url);
|
||||
|
||||
Iterator<MediaFrame> tryLoad(String url) throws Exception;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.media;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
public class TimedMediaFrameIterator implements Iterator<BufferedImage> {
|
||||
|
||||
private final Iterator<MediaFrame> backing;
|
||||
private final int stepMs;
|
||||
|
||||
private MediaFrame current;
|
||||
private int elapsedInFrame = 0;
|
||||
private boolean finished = false;
|
||||
|
||||
public TimedMediaFrameIterator(Iterator<MediaFrame> backing, int stepMs) {
|
||||
this.backing = backing;
|
||||
this.stepMs = stepMs;
|
||||
|
||||
if (backing.hasNext()) {
|
||||
this.current = backing.next();
|
||||
} else {
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return !finished;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedImage next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
MediaFrame result = current;
|
||||
if (current.getDurationMs() == -1) {
|
||||
return result.getImage();
|
||||
}
|
||||
elapsedInFrame += stepMs;
|
||||
while (current.getDurationMs() != -1 && elapsedInFrame >= current.getDurationMs()) {
|
||||
elapsedInFrame -= current.getDurationMs();
|
||||
if (backing.hasNext()) {
|
||||
current = backing.next();
|
||||
} else {
|
||||
finished = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result.getImage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.metrics;
|
||||
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import com.loohp.imageframe.objectholders.ImageMap;
|
||||
import com.loohp.imageframe.utils.PlayerUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.map.MapCursor;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
@SuppressWarnings("Convert2Lambda")
|
||||
public class Charts {
|
||||
|
||||
public static void setup(Metrics metrics) {
|
||||
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("total_images_created", new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
return ImageFrame.imageMapManager.getMaps().size();
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("total_maps_created", new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
return ImageFrame.imageMapManager.getMaps().stream().mapToInt(each -> each.getMapViews().size()).sum();
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("images_created_by_type_id", new Callable<Map<String, Integer>>() {
|
||||
@Override
|
||||
public Map<String, Integer> call() {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (ImageMap imageMap : ImageFrame.imageMapManager.getMaps()) {
|
||||
String type = imageMap.getType().asString();
|
||||
valueMap.merge(type, 1, Integer::sum);
|
||||
}
|
||||
return valueMap;
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("images_created_by_type", new Callable<Map<String, Integer>>() {
|
||||
@Override
|
||||
public Map<String, Integer> call() {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (ImageMap imageMap : ImageFrame.imageMapManager.getMaps()) {
|
||||
String type = imageMap.getLegacyType();
|
||||
if (type != null) {
|
||||
valueMap.merge(type, 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
return valueMap;
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("total_markers_created", new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
return ImageFrame.imageMapManager.getMaps().stream().flatMap(each -> each.getMapMarkers().stream()).mapToInt(each -> each.size()).sum();
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("markers_created_by_type", new Callable<Map<String, Integer>>() {
|
||||
@Override
|
||||
public Map<String, Integer> call() {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (ImageMap imageMap : ImageFrame.imageMapManager.getMaps()) {
|
||||
for (Map<String, MapCursor> map : imageMap.getMapMarkers()) {
|
||||
for (MapCursor mapCursor : map.values()) {
|
||||
String type = mapCursor.getType().name().toLowerCase();
|
||||
valueMap.merge(type, 1, Integer::sum);
|
||||
}
|
||||
}
|
||||
}
|
||||
return valueMap;
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("item_frames_made_invisible_in_last_interval", new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
long value = ImageFrame.invisibleFrameManager.getItemFramesMadeInvisible().getAndSet(0);
|
||||
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) value;
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("invisible_item_frames_placed_in_last_interval", new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
long value = ImageFrame.invisibleFrameManager.getInvisibleItemFramesPlaced().getAndSet(0);
|
||||
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) value;
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("embedded_service_image_uploaded_in_last_interval", new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
long value = ImageFrame.imageUploadManager.getImagesUploadedCounter().getAndSet(0);
|
||||
return value > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) value;
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SimplePie("storage_type", new Callable<String>() {
|
||||
@Override
|
||||
public String call() {
|
||||
return ImageFrame.imageFrameStorage.getLoader().getIdentifier().asString();
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SingleLineChart("players_with_imageframe_client", new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() {
|
||||
return ImageFrame.customClientNetworkManager.getPlayers().size();
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.SimplePie("imageframe_language_by_server", new Callable<String>() {
|
||||
@Override
|
||||
public String call() {
|
||||
return ImageFrame.language;
|
||||
}
|
||||
}));
|
||||
|
||||
metrics.addCustomChart(new Metrics.AdvancedPie("imageframe_language_by_players", new Callable<Map<String, Integer>>() {
|
||||
@Override
|
||||
public Map<String, Integer> call() {
|
||||
Map<String, Integer> valueMap = new HashMap<>();
|
||||
for (Player player : Bukkit.getOnlinePlayers()) {
|
||||
String language = PlayerUtils.getPlayerLanguage(player);
|
||||
valueMap.merge(language, 1, Integer::sum);
|
||||
}
|
||||
return valueMap;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
}
|
||||
+166
-122
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2022. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2022. Contributors
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
@@ -34,6 +34,13 @@
|
||||
*/
|
||||
package com.loohp.imageframe.metrics;
|
||||
|
||||
import com.loohp.platformscheduler.Scheduler;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@@ -51,8 +58,8 @@ import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
@@ -60,12 +67,6 @@ import java.util.function.Supplier;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class Metrics {
|
||||
|
||||
@@ -80,7 +81,7 @@ public class Metrics {
|
||||
* @param serviceId The id of the service. It can be found at <a
|
||||
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
|
||||
*/
|
||||
public Metrics(JavaPlugin plugin, int serviceId) {
|
||||
public Metrics(Plugin plugin, int serviceId) {
|
||||
this.plugin = plugin;
|
||||
// Get the config file
|
||||
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
|
||||
@@ -113,21 +114,41 @@ public class Metrics {
|
||||
boolean logErrors = config.getBoolean("logFailedRequests", false);
|
||||
boolean logSentData = config.getBoolean("logSentData", false);
|
||||
boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false);
|
||||
boolean isFolia = false;
|
||||
try {
|
||||
isFolia = Class.forName("io.papermc.paper.threadedregions.RegionizedServer") != null;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
metricsBase =
|
||||
new MetricsBase(
|
||||
new // See https://github.com/Bastian/bstats-metrics/pull/126
|
||||
// See https://github.com/Bastian/bstats-metrics/pull/126
|
||||
// See https://github.com/Bastian/bstats-metrics/pull/126
|
||||
// See https://github.com/Bastian/bstats-metrics/pull/126
|
||||
// See https://github.com/Bastian/bstats-metrics/pull/126
|
||||
// See https://github.com/Bastian/bstats-metrics/pull/126
|
||||
// See https://github.com/Bastian/bstats-metrics/pull/126
|
||||
MetricsBase(
|
||||
"bukkit",
|
||||
serverUUID,
|
||||
serviceId,
|
||||
enabled,
|
||||
this::appendPlatformData,
|
||||
this::appendServiceData,
|
||||
submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask),
|
||||
isFolia
|
||||
? null
|
||||
: submitDataTask -> Scheduler.runTask(plugin, submitDataTask),
|
||||
plugin::isEnabled,
|
||||
(message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error),
|
||||
(message) -> this.plugin.getLogger().log(Level.INFO, message),
|
||||
logErrors,
|
||||
logSentData,
|
||||
logResponseStatusText);
|
||||
logResponseStatusText,
|
||||
false);
|
||||
}
|
||||
|
||||
/** Shuts down the underlying scheduler service. */
|
||||
public void shutdown() {
|
||||
metricsBase.shutdown();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -173,13 +194,12 @@ public class Metrics {
|
||||
public static class MetricsBase {
|
||||
|
||||
/** The version of the Metrics class. */
|
||||
public static final String METRICS_VERSION = "3.0.0";
|
||||
|
||||
private static final ScheduledExecutorService scheduler =
|
||||
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
|
||||
public static final String METRICS_VERSION = "3.1.0";
|
||||
|
||||
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";
|
||||
|
||||
private final ScheduledExecutorService scheduler;
|
||||
|
||||
private final String platform;
|
||||
|
||||
private final String serverUuid;
|
||||
@@ -228,6 +248,7 @@ public class Metrics {
|
||||
* @param logErrors Whether or not errors should be logged.
|
||||
* @param logSentData Whether or not the sent data should be logged.
|
||||
* @param logResponseStatusText Whether or not the response status text should be logged.
|
||||
* @param skipRelocateCheck Whether or not the relocate check should be skipped.
|
||||
*/
|
||||
public MetricsBase(
|
||||
String platform,
|
||||
@@ -242,7 +263,22 @@ public class Metrics {
|
||||
Consumer<String> infoLogger,
|
||||
boolean logErrors,
|
||||
boolean logSentData,
|
||||
boolean logResponseStatusText) {
|
||||
boolean logResponseStatusText,
|
||||
boolean skipRelocateCheck) {
|
||||
ScheduledThreadPoolExecutor scheduler =
|
||||
new ScheduledThreadPoolExecutor(
|
||||
1,
|
||||
task -> {
|
||||
Thread thread = new Thread(task, "bStats-Metrics");
|
||||
thread.setDaemon(true);
|
||||
return thread;
|
||||
});
|
||||
// We want delayed tasks (non-periodic) that will execute in the future to be
|
||||
// cancelled when the scheduler is shutdown.
|
||||
// Otherwise, we risk preventing the server from shutting down even when
|
||||
// MetricsBase#shutdown() is called
|
||||
scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
|
||||
this.scheduler = scheduler;
|
||||
this.platform = platform;
|
||||
this.serverUuid = serverUuid;
|
||||
this.serviceId = serviceId;
|
||||
@@ -256,9 +292,12 @@ public class Metrics {
|
||||
this.logErrors = logErrors;
|
||||
this.logSentData = logSentData;
|
||||
this.logResponseStatusText = logResponseStatusText;
|
||||
checkRelocation();
|
||||
if (!skipRelocateCheck) {
|
||||
checkRelocation();
|
||||
}
|
||||
if (enabled) {
|
||||
// WARNING: Removing the option to opt-out will get your plugin banned from bStats
|
||||
// WARNING: Removing the option to opt-out will get your plugin banned from
|
||||
// bStats
|
||||
startSubmitting();
|
||||
}
|
||||
}
|
||||
@@ -267,6 +306,10 @@ public class Metrics {
|
||||
this.customCharts.add(chart);
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
scheduler.shutdown();
|
||||
}
|
||||
|
||||
private void startSubmitting() {
|
||||
final Runnable submitTask =
|
||||
() -> {
|
||||
@@ -281,13 +324,14 @@ public class Metrics {
|
||||
this.submitData();
|
||||
}
|
||||
};
|
||||
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution
|
||||
// of requests on the
|
||||
// bStats backend. To circumvent this problem, we introduce some randomness into the initial
|
||||
// and second delay.
|
||||
// WARNING: You must not modify and part of this Metrics class, including the submit delay or
|
||||
// frequency!
|
||||
// WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it!
|
||||
// Many servers tend to restart at a fixed time at xx:00 which causes an uneven
|
||||
// distribution of requests on the
|
||||
// bStats backend. To circumvent this problem, we introduce some randomness into
|
||||
// the initial and second delay.
|
||||
// WARNING: You must not modify and part of this Metrics class, including the
|
||||
// submit delay or frequency!
|
||||
// WARNING: Modifying this code will get your plugin banned on bStats. Just
|
||||
// don't do it!
|
||||
long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3));
|
||||
long secondDelay = (long) (1000 * 60 * (Math.random() * 30));
|
||||
scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS);
|
||||
@@ -362,14 +406,14 @@ public class Metrics {
|
||||
// You can use the property to disable the check in your test environment
|
||||
if (System.getProperty("bstats.relocatecheck") == null
|
||||
|| !System.getProperty("bstats.relocatecheck").equals("false")) {
|
||||
// Maven's Relocate is clever and changes strings, too. So we have to use this little
|
||||
// "trick" ... :D
|
||||
// Maven's Relocate is clever and changes strings, too. So we have to use this
|
||||
// little "trick" ... :D
|
||||
final String defaultPackage =
|
||||
new String(new byte[] {'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's'});
|
||||
final String examplePackage =
|
||||
new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
|
||||
// We want to make sure no one just copy & pastes the example and uses the wrong package
|
||||
// names
|
||||
// We want to make sure no one just copy & pastes the example and uses the wrong
|
||||
// package names
|
||||
if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage)
|
||||
|| MetricsBase.class.getPackage().getName().startsWith(examplePackage)) {
|
||||
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
|
||||
@@ -395,6 +439,72 @@ public class Metrics {
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdvancedBarChart extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, int[]>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, int[]> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, int[]> entry : map.entrySet()) {
|
||||
if (entry.getValue().length == 0) {
|
||||
// Skip this invalid
|
||||
continue;
|
||||
}
|
||||
allSkipped = false;
|
||||
valuesBuilder.appendField(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SimplePie extends CustomChart {
|
||||
|
||||
private final Callable<String> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public SimplePie(String chartId, Callable<String> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
String value = callable.call();
|
||||
if (value == null || value.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
return new JsonObjectBuilder().appendField("value", value).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class DrilldownPie extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Map<String, Integer>>> callable;
|
||||
@@ -439,9 +549,9 @@ public class Metrics {
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdvancedPie extends CustomChart {
|
||||
public static class SingleLineChart extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Integer>> callable;
|
||||
private final Callable<Integer> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
@@ -449,33 +559,19 @@ public class Metrics {
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
public SingleLineChart(String chartId, Callable<Integer> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
int value = callable.call();
|
||||
if (value == 0) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
if (entry.getValue() == 0) {
|
||||
// Skip this invalid
|
||||
continue;
|
||||
}
|
||||
allSkipped = false;
|
||||
valuesBuilder.appendField(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||
return new JsonObjectBuilder().appendField("value", value).build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,7 +615,7 @@ public class Metrics {
|
||||
}
|
||||
}
|
||||
|
||||
public static class SimpleBarChart extends CustomChart {
|
||||
public static class AdvancedPie extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, Integer>> callable;
|
||||
|
||||
@@ -529,7 +625,7 @@ public class Metrics {
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
@@ -542,8 +638,18 @@ public class Metrics {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
|
||||
if (entry.getValue() == 0) {
|
||||
// Skip this invalid
|
||||
continue;
|
||||
}
|
||||
allSkipped = false;
|
||||
valuesBuilder.appendField(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||
}
|
||||
@@ -583,9 +689,9 @@ public class Metrics {
|
||||
protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception;
|
||||
}
|
||||
|
||||
public static class SimplePie extends CustomChart {
|
||||
public static class SimpleBarChart extends CustomChart {
|
||||
|
||||
private final Callable<String> callable;
|
||||
private final Callable<Map<String, Integer>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
@@ -593,33 +699,7 @@ public class Metrics {
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public SimplePie(String chartId, Callable<String> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
String value = callable.call();
|
||||
if (value == null || value.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
return new JsonObjectBuilder().appendField("value", value).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class AdvancedBarChart extends CustomChart {
|
||||
|
||||
private final Callable<Map<String, int[]>> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
|
||||
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
@@ -627,54 +707,18 @@ public class Metrics {
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, int[]> map = callable.call();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, int[]> entry : map.entrySet()) {
|
||||
if (entry.getValue().length == 0) {
|
||||
// Skip this invalid
|
||||
continue;
|
||||
}
|
||||
allSkipped = false;
|
||||
valuesBuilder.appendField(entry.getKey(), entry.getValue());
|
||||
}
|
||||
if (allSkipped) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
for (Map.Entry<String, Integer> entry : map.entrySet()) {
|
||||
valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()});
|
||||
}
|
||||
return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build();
|
||||
}
|
||||
}
|
||||
|
||||
public static class SingleLineChart extends CustomChart {
|
||||
|
||||
private final Callable<Integer> callable;
|
||||
|
||||
/**
|
||||
* Class constructor.
|
||||
*
|
||||
* @param chartId The id of the chart.
|
||||
* @param callable The callable which is used to request the chart data.
|
||||
*/
|
||||
public SingleLineChart(String chartId, Callable<Integer> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
int value = callable.call();
|
||||
if (value == 0) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
return new JsonObjectBuilder().appendField("value", value).build();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An extremely simple JSON builder.
|
||||
*
|
||||
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.migration;
|
||||
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import com.loohp.imageframe.objectholders.DitheringType;
|
||||
import com.loohp.imageframe.objectholders.ImageMapLoaders;
|
||||
import com.loohp.imageframe.objectholders.NonUpdatableImageMapCreateInfo;
|
||||
import com.loohp.imageframe.objectholders.NonUpdatableStaticImageMap;
|
||||
import com.loohp.imageframe.objectholders.NonUpdatableStaticImageMapLoader;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.UUID;
|
||||
|
||||
public class DrMapMigration implements ExternalPluginMigration {
|
||||
|
||||
public static final String PLUGIN_NAME = "DrMap";
|
||||
|
||||
@Override
|
||||
public String externalPluginName() {
|
||||
return PLUGIN_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requirePlayer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(UUID owner) {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled(PLUGIN_NAME)) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] DrMap must be disabled for migration to begin");
|
||||
return;
|
||||
}
|
||||
File migrationMarker = new File(ImageFrame.plugin.getDataFolder().getParent() + "/DrMap/imageframe-migrated.bin");
|
||||
if (migrationMarker.exists()) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[ImageFrame] DrMap data already marked as migrated");
|
||||
return;
|
||||
}
|
||||
File folder = new File(ImageFrame.plugin.getDataFolder().getParent() + "/DrMap/images");
|
||||
if (!folder.exists() || !folder.isDirectory()) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] DrMap plugin data folder not found");
|
||||
return;
|
||||
}
|
||||
NonUpdatableStaticImageMapLoader loader = ImageMapLoaders.getLoader(NonUpdatableStaticImageMapLoader.class);
|
||||
for (File file : folder.listFiles()) {
|
||||
try {
|
||||
BufferedImage[] images = new BufferedImage[] {ImageIO.read(file)};
|
||||
int mapId = Integer.parseInt(file.getName().substring(0, file.getName().lastIndexOf(".")));
|
||||
String name = "DrMap_" + mapId;
|
||||
NonUpdatableStaticImageMap imageMap = loader.create(new NonUpdatableImageMapCreateInfo(ImageFrame.imageMapManager, name, images, Collections.singletonList(mapId), 1, 1, DitheringType.NEAREST_COLOR, owner)).get();
|
||||
ImageFrame.imageMapManager.addMap(imageMap);
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[ImageFrame] Migrated DrMap " + file.getName() + " to " + name);
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] Unable to migrate DrMap " + file.getAbsolutePath());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
try {
|
||||
migrationMarker.createNewFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[ImageFrame] DrMap migration complete!");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.migration;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public interface ExternalPluginMigration {
|
||||
|
||||
String externalPluginName();
|
||||
|
||||
boolean requirePlayer();
|
||||
|
||||
default void migrate() {
|
||||
if (requirePlayer()) {
|
||||
throw new IllegalStateException("Player required!");
|
||||
}
|
||||
migrate(null);
|
||||
}
|
||||
|
||||
void migrate(UUID player);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*
|
||||
* This file is part of ImageFrame.
|
||||
*
|
||||
* Copyright (C) 2026. LoohpJames <jamesloohp@gmail.com>
|
||||
* Copyright (C) 2026. Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.loohp.imageframe.migration;
|
||||
|
||||
import com.loohp.imageframe.ImageFrame;
|
||||
import com.loohp.imageframe.objectholders.DitheringType;
|
||||
import com.loohp.imageframe.objectholders.ImageMapLoaders;
|
||||
import com.loohp.imageframe.objectholders.NonUpdatableImageMapCreateInfo;
|
||||
import com.loohp.imageframe.objectholders.NonUpdatableStaticImageMap;
|
||||
import com.loohp.imageframe.objectholders.NonUpdatableStaticImageMapLoader;
|
||||
import com.loohp.imageframe.utils.MapUtils;
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.simpleyaml.configuration.file.YamlConfiguration;
|
||||
import org.simpleyaml.configuration.serialization.ConfigurationSerializable;
|
||||
import org.simpleyaml.configuration.serialization.ConfigurationSerialization;
|
||||
import org.simpleyaml.configuration.serialization.SerializableAs;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ImageMapMigration implements ExternalPluginMigration {
|
||||
|
||||
public static final String PLUGIN_NAME = "ImageMaps";
|
||||
|
||||
@Override
|
||||
public String externalPluginName() {
|
||||
return PLUGIN_NAME;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requirePlayer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(UUID owner) {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled(PLUGIN_NAME)) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] ImageMaps must be disabled for migration to begin");
|
||||
return;
|
||||
}
|
||||
File migrationMarker = new File(ImageFrame.plugin.getDataFolder().getParent() + "/ImageMaps/imageframe-migrated.bin");
|
||||
if (migrationMarker.exists()) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.YELLOW + "[ImageFrame] ImageMaps data already marked as migrated");
|
||||
return;
|
||||
}
|
||||
File folder = new File(ImageFrame.plugin.getDataFolder().getParent() + "/ImageMaps/images");
|
||||
if (!folder.exists() || !folder.isDirectory()) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] ImageMaps plugin data folder not found");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
NonUpdatableStaticImageMapLoader loader = ImageMapLoaders.getLoader(NonUpdatableStaticImageMapLoader.class);
|
||||
ConfigurationSerialization.registerClass(ImageMap.class);
|
||||
YamlConfiguration mapConfig = YamlConfiguration.loadConfiguration(new File(ImageFrame.plugin.getDataFolder().getParent() + "/ImageMaps/maps.yml"));
|
||||
for (Map.Entry<String, Object> entry : mapConfig.getConfigurationSection("maps").getValues(false).entrySet()) {
|
||||
String key = entry.getKey();
|
||||
try {
|
||||
int mapId = Integer.parseInt(key);
|
||||
ImageMap data = (ImageMap) entry.getValue();
|
||||
String fileName = data.getFilename();
|
||||
BufferedImage[] images = new BufferedImage[] {MapUtils.getSubImage(ImageIO.read(new File(folder, fileName)), data.getX(), data.getY())};
|
||||
String name = "ImageMaps_" + mapId;
|
||||
NonUpdatableStaticImageMap imageMap = loader.create(new NonUpdatableImageMapCreateInfo(ImageFrame.imageMapManager, name, images, Collections.singletonList(mapId), 1, 1, DitheringType.NEAREST_COLOR, owner)).get();
|
||||
ImageFrame.imageMapManager.addMap(imageMap);
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[ImageFrame] Migrated ImageMaps " + key + " to " + name);
|
||||
} catch (Exception e) {
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "[ImageFrame] Unable to migrate ImageMaps " + key);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
migrationMarker.createNewFile();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage(ChatColor.GREEN + "[ImageFrame] ImageMaps migration complete!");
|
||||
}
|
||||
|
||||
@SerializableAs("ImageMaps.Map")
|
||||
public static class ImageMap implements ConfigurationSerializable {
|
||||
|
||||
private final String filename;
|
||||
private final int x;
|
||||
private final int y;
|
||||
private final double scale;
|
||||
|
||||
public ImageMap(String filename, int x, int y, double scale) {
|
||||
this.filename = filename;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.scale = scale;
|
||||
}
|
||||
|
||||
public ImageMap(Map<?, ?> map) {
|
||||
this.filename = map.get("image").toString();
|
||||
this.x = (Integer) map.get("x");
|
||||
this.y = (Integer) map.get("y");
|
||||
this.scale = (Double) map.get("scale");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> serialize() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("image", filename);
|
||||
map.put("x", x);
|
||||
map.put("y", y);
|
||||
map.put("scale", scale);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public String getFilename() {
|
||||
return filename;
|
||||
}
|
||||
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
public int getY() {
|
||||
return y;
|
||||
}
|
||||
|
||||
public double getScale() {
|
||||
return scale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((filename == null) ? 0 : filename.hashCode());
|
||||
long temp;
|
||||
temp = Double.doubleToLongBits(scale);
|
||||
result = prime * result + (int) (temp ^ (temp >>> 32));
|
||||
result = prime * result + x;
|
||||
result = prime * result + y;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!(obj instanceof ImageMap))
|
||||
return false;
|
||||
ImageMap other = (ImageMap) obj;
|
||||
if (filename == null) {
|
||||
if (other.filename != null)
|
||||
return false;
|
||||
} else if (!filename.equals(other.filename))
|
||||
return false;
|
||||
if (Double.doubleToLongBits(scale) != Double.doubleToLongBits(other.scale))
|
||||
return false;
|
||||
if (x != other.x)
|
||||
return false;
|
||||
if (y != other.y)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Loaded 100 of 217 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user