Skip to content

Commit

Permalink
Merge pull request #94 from josemmo/develop
Browse files Browse the repository at this point in the history
v1.2.10
  • Loading branch information
josemmo authored May 7, 2023
2 parents a786471 + 8eed5bd commit 494e18b
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 77 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ jobs:
VERSION: ${{ matrix.version }}
run: |
if [ $VERSION == "1.19.4" ]; then
url="https://ci.dmulloy2.net/job/ProtocolLib/lastStableBuild/artifact/target/ProtocolLib.jar"
url="https://ci.dmulloy2.net/job/ProtocolLib/lastSuccessfulBuild/artifact/build/libs/ProtocolLib.jar"
else
url="https://github.com/dmulloy2/ProtocolLib/releases/download/4.8.0/ProtocolLib.jar"
fi
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![bStats Servers](https://img.shields.io/bstats/servers/10243)](https://bstats.org/plugin/bukkit/Yamipa/10243)
[![License](https://img.shields.io/github/license/josemmo/yamipa)](LICENSE)

Yamipa is an Spigot plugin that allows players to place images (even **animated**!) on any surface in your Minecraft server
Yamipa is a Spigot plugin that allows players to place images (even **animated**!) on any surface in your Minecraft server
without having to install any local client mod.

It is designed with performance and compatibility in mind, so even the most low-specs servers should be able to run it.
Expand All @@ -24,8 +24,9 @@ Download the JAR file for the [latest release](https://github.com/josemmo/yamipa
### Requirements
Before installing Yamipa make sure you meet the following requirements:

- CraftBukkit, Spigot or PaperMC v1.16 or higher
- [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) v4.6.1 or higher
- CraftBukkit, Spigot or PaperMC 1.16 or higher
- [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) v4.8.0 or higher
(latest [dev build](https://ci.dmulloy2.net/job/ProtocolLib/lastSuccessfulBuild/) for 1.19.4)

Here are the Minecraft distributions where Yamipa should be able to run:
| Minecraft version | CraftBukkit | Spigot | PaperMC |
Expand Down Expand Up @@ -132,6 +133,7 @@ The supported plugins are:

- [WorldGuard](https://enginehub.org/worldguard/)
- [GriefPrevention](https://www.spigotmc.org/resources/griefprevention.1884/)
- [Towny Advanced](https://townyadvanced.github.io/)

## Flags
Images from this plugin have a set of boolean attributes called "flags" that modify its behavior. Possible values are:
Expand Down
10 changes: 8 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>io.josemmo.bukkit.plugin</groupId>
<artifactId>YamipaPlugin</artifactId>
<version>1.2.9</version>
<version>1.2.10</version>

<properties>
<maven.compiler.source>8</maven.compiler.source>
Expand Down Expand Up @@ -59,7 +59,7 @@
<dependency>
<groupId>org.bstats</groupId>
<artifactId>bstats-bukkit</artifactId>
<version>3.0.1</version>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.sk89q.worldguard</groupId>
Expand All @@ -73,6 +73,12 @@
<version>16.18.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.TownyAdvanced</groupId>
<artifactId>towny</artifactId>
<version>0.98.6.25</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ public static boolean placeImage(

// Make sure image can be placed
for (Location loc : fakeImage.getAllLocations()) {
if (!Permissions.canEditBlock(player, loc)) {
if (!Permissions.canBuild(player, loc)) {
ActionBar.send(player, ChatColor.RED + "You're not allowed to place an image here!");
return false;
}
Expand Down Expand Up @@ -260,7 +260,7 @@ public static void removeImage(@NotNull Player player) {
public static boolean removeImage(@NotNull Player player, @NotNull FakeImage image) {
// Check block permissions
for (Location loc : image.getAllLocations()) {
if (!Permissions.canEditBlock(player, loc)) {
if (!Permissions.canDestroy(player, loc)) {
ActionBar.send(player, ChatColor.RED + "You're not allowed to remove this image!");
return false;
}
Expand Down Expand Up @@ -299,7 +299,7 @@ public static void clearImages(
Player senderAsPlayer = (Player) sender;
images.removeIf(image -> {
for (Location loc : image.getAllLocations()) {
if (!Permissions.canEditBlock(senderAsPlayer, loc)) {
if (!Permissions.canDestroy(senderAsPlayer, loc)) {
return true;
}
}
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/io/josemmo/bukkit/plugin/renderer/FakeEntity.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package io.josemmo.bukkit.plugin.renderer;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import io.josemmo.bukkit.plugin.YamipaPlugin;
import io.josemmo.bukkit.plugin.utils.Internals;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.lang.reflect.Field;
Expand Down Expand Up @@ -94,6 +96,23 @@ protected static void tryToSendPacket(@NotNull Player player, @NotNull PacketCon
}
}

/**
* Try to send several packets
* @param player Player who will receive the packets
* @param packets Packets to send
*/
protected static void tryToSendPackets(@NotNull Player player, @NotNull Iterable<PacketContainer> packets) {
if (Internals.MINECRAFT_VERSION < 19.4f) {
for (PacketContainer packet : packets) {
tryToSendPacket(player, packet);
}
} else {
PacketContainer container = new PacketContainer(PacketType.Play.Server.BUNDLE);
container.getPacketBundles().write(0, packets);
tryToSendPacket(player, container);
}
}

/**
* Try to run asynchronous task
* @param callback Callback to execute
Expand Down
24 changes: 20 additions & 4 deletions src/main/java/io/josemmo/bukkit/plugin/renderer/FakeImage.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.josemmo.bukkit.plugin.renderer;

import com.comphenix.protocol.events.PacketContainer;
import io.josemmo.bukkit.plugin.storage.ImageFile;
import io.josemmo.bukkit.plugin.utils.DirectionUtils;
import org.bukkit.Location;
Expand All @@ -12,6 +13,7 @@
import org.jetbrains.annotations.Nullable;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
Expand Down Expand Up @@ -429,11 +431,19 @@ public void spawn(@NotNull Player player) {
* @param player Player instance
*/
private void spawnOnceLoaded(@NotNull Player player) {
String playerName = player.getName();
observingPlayers.add(player);

// Prepare packets to send
List<PacketContainer> packets = new ArrayList<>();
for (FakeItemFrame frame : frames) {
frame.spawn(player);
frame.render(player, 0);
packets.add(frame.getSpawnPacket());
packets.addAll(frame.getRenderPackets(player, 0));
plugin.fine("Spawned FakeItemFrame#" + frame.getId() + " for Player#" + playerName);
}

// Send packets
tryToSendPackets(player, packets);
}

/**
Expand All @@ -459,9 +469,13 @@ public void destroy(@Nullable Player player) {
if (frames != null) {
Set<Player> targets = (player == null) ? observingPlayers : Collections.singleton(player);
for (Player target : targets) {
String targetName = target.getName();
List<PacketContainer> packets = new ArrayList<>();
for (FakeItemFrame frame : frames) {
frame.destroy(target);
packets.add(frame.getDestroyPacket());
plugin.fine("Destroyed FakeItemFrame#" + frame.getId() + " for Player#" + targetName);
}
tryToSendPackets(target, packets);
}
}

Expand Down Expand Up @@ -521,9 +535,11 @@ private void nextStep() {
currentStep = (currentStep + 1) % numOfSteps;
try {
for (Player player : observingPlayers) {
List<PacketContainer> packets = new ArrayList<>();
for (FakeItemFrame frame : frames) {
frame.render(player, currentStep);
packets.addAll(frame.getRenderPackets(player, currentStep));
}
tryToSendPackets(player, packets);
}
} catch (ConcurrentModificationException e) {
// We can safely ignore this exception as it will just result
Expand Down
55 changes: 35 additions & 20 deletions src/main/java/io/josemmo/bukkit/plugin/renderer/FakeItemFrame.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.josemmo.bukkit.plugin.renderer;

import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
Expand All @@ -15,6 +16,8 @@
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

public class FakeItemFrame extends FakeEntity {
Expand Down Expand Up @@ -67,10 +70,18 @@ public FakeItemFrame(
}

/**
* Spawn empty item frame in player's client
* @param player Player instance
* Get frame ID
* @return Frame ID
*/
public void spawn(@NotNull Player player) {
public int getId() {
return id;
}

/**
* Get entity spawn packet
* @return Spawn packet
*/
public @NotNull SpawnEntityPacket getSpawnPacket() {
// Calculate frame position in relation to target block
double x = location.getBlockX();
double y = location.getBlockY();
Expand Down Expand Up @@ -115,18 +126,23 @@ public void spawn(@NotNull Player player) {
.setPosition(x, y, z)
.setRotation(pitch, yaw)
.setData(orientation);
tryToSendPacket(player, framePacket);
plugin.fine("Spawned FakeItemFrame#" + this.id + " for Player#" + player.getName());

return framePacket;
}

/**
* Send frame of animation to player
* @param player Player instance
* @param step Map step to send
* Get frame of animation packets
* @param player Player who is expected to receive packets (for caching reasons)
* @param step Map step
*/
public void render(@NotNull Player player, int step) {
// Send map pixels
maps[step].sendPixels(player);
public @NotNull List<PacketContainer> getRenderPackets(@NotNull Player player, int step) {
List<PacketContainer> packets = new ArrayList<>(2);

// Enqueue map pixels packet (if needed)
boolean mustSendPixels = maps[step].requestResend(player);
if (mustSendPixels) {
packets.add(maps[step].getPixelsPacket());
}

// Create and attach filled map
ItemStack itemStack = MinecraftReflection.getBukkitItemStack(new ItemStack(Material.FILLED_MAP));
Expand All @@ -135,25 +151,24 @@ public void render(@NotNull Player player, int step) {
NbtFactory.setItemTag(itemStack, itemStackNbt);

// Build entity metadata packet
EntityMetadataPacket mapPacket = new EntityMetadataPacket();
mapPacket.setId(id)
EntityMetadataPacket metadataPacket = new EntityMetadataPacket();
metadataPacket.setId(id)
.setInvisible(true)
.setItem(itemStack)
.setRotation(rotation)
.build();
packets.add(metadataPacket);

// Send animation status update
tryToSendPacket(player, mapPacket);
return packets;
}

/**
* Destroy item frame from player's client
* @param player Player instance
* Get destroy item frame packet
* @return Destroy packet
*/
public void destroy(@NotNull Player player) {
public @NotNull DestroyEntityPacket getDestroyPacket() {
DestroyEntityPacket destroyPacket = new DestroyEntityPacket();
destroyPacket.setId(id);
tryToSendPacket(player, destroyPacket);
plugin.fine("Destroyed FakeItemFrame#" + this.id + " for Player#" + player.getName());
return destroyPacket;
}
}
29 changes: 18 additions & 11 deletions src/main/java/io/josemmo/bukkit/plugin/renderer/FakeMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -120,30 +120,37 @@ public byte[] getPixels() {
}

/**
* Send map pixels to player
* @param player Player instance
* Request re-send of map pixels
* @param player Player who is expected to receive pixels
* @return Whether re-send authorization was granted or not
*/
public void sendPixels(@NotNull Player player) {
public boolean requestResend(@NotNull Player player) {
UUID uuid = player.getUniqueId();
long now = Instant.now().getEpochSecond();

// Avoid re-sending pixels too frequently
// Has enough time passed since last re-send?
long last = lastPlayerSendTime.getOrDefault(uuid, 0L);
if ((now-last) <= RESEND_THRESHOLD && (player.getLastPlayed()/1000) < last) {
return;
return false;
}

// Create map data packet
// Authorize re-send and update latest timestamp
lastPlayerSendTime.put(uuid, now);
plugin.fine("Granted sending pixels for FakeMap#" + id + " to Player#" + player.getName());
return true;
}

/**
* Get map pixels packet
* @return Map pixels packet
*/
public @NotNull MapDataPacket getPixelsPacket() {
MapDataPacket mapDataPacket = new MapDataPacket();
mapDataPacket.setId(id)
.setScale(0) // Fully zoomed-in
.setLocked(true)
.setArea(DIMENSION, DIMENSION, 0, 0)
.setPixels(pixels);

// Send packet
tryToSendPacket(player, mapDataPacket);
lastPlayerSendTime.put(uuid, now);
plugin.fine("Sent pixels for FakeMap#" + id + " to Player#" + player.getName());
return mapDataPacket;
}
}
Loading

0 comments on commit 494e18b

Please sign in to comment.