ImageOnMap/src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java

383 lines
15 KiB
Java
Raw Normal View History

/*
* Copyright or © or Copr. Moribus (2013)
* Copyright or © or Copr. ProkopyL <prokopylmc@gmail.com> (2015)
* Copyright or © or Copr. Amaury Carrade <amaury@carrade.eu> (2016 2021)
* Copyright or © or Copr. Vlammar <valentin.jabre@gmail.com> (2019 2021)
*
* This software is a computer program whose purpose is to allow insertion of
* custom images in a Minecraft world.
*
* This software is governed by the CeCILL license under French law and
* abiding by the rules of distribution of free software. You can use,
* modify and/ or redistribute the software under the terms of the CeCILL
* license as circulated by CEA, CNRS and INRIA at the following URL
* "http://www.cecill.info".
*
* As a counterpart to the access to the source code and rights to copy,
* modify and redistribute granted by the license, users are provided only
* with a limited warranty and the software's author, the holder of the
* economic rights, and the successive licensors have only limited
* liability.
*
* In this respect, the user's attention is drawn to the risks associated
* with loading, using, modifying and/or developing or reproducing the
* software by the user in light of its specific status of free software,
* that may mean that it is complicated to manipulate, and that also
* therefore means that it is reserved for developers and experienced
* professionals having in-depth computer knowledge. Users are therefore
* encouraged to load and test the software's suitability as regards their
* requirements in conditions enabling the security of their systems and/or
* data to be ensured and, more generally, to use and operate it in the
* same conditions as regards security.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL license and that you accept its terms.
*/
package fr.moribus.imageonmap.ui;
import com.google.common.collect.ImmutableMap;
import fr.moribus.imageonmap.image.MapInitEvent;
import fr.moribus.imageonmap.map.ImageMap;
import fr.moribus.imageonmap.map.MapManager;
import fr.moribus.imageonmap.map.PosterMap;
import fr.zcraft.quartzlib.components.i18n.I;
import fr.zcraft.quartzlib.components.nbt.NBT;
import fr.zcraft.quartzlib.components.nbt.NBTCompound;
import fr.zcraft.quartzlib.components.nbt.NBTException;
import fr.zcraft.quartzlib.components.nbt.NBTList;
import fr.zcraft.quartzlib.tools.PluginLogger;
import fr.zcraft.quartzlib.tools.items.ItemStackBuilder;
import fr.zcraft.quartzlib.tools.reflection.NMSException;
import fr.zcraft.quartzlib.tools.runners.RunTask;
import fr.zcraft.quartzlib.tools.text.MessageSender;
import fr.zcraft.quartzlib.tools.world.FlatLocation;
import fr.zcraft.quartzlib.tools.world.WorldUtils;
import org.bukkit.ChatColor;
import org.bukkit.Color;
import org.bukkit.Material;
import org.bukkit.Rotation;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.event.player.PlayerInteractEntityEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.MapMeta;
2020-12-10 22:13:05 +01:00
public abstract class SplatterMapManager {
2020-12-09 21:56:55 +01:00
private SplatterMapManager() {
}
2020-12-10 22:13:05 +01:00
public static ItemStack makeSplatterMap(PosterMap map) {
2020-12-09 21:56:55 +01:00
final ItemStack splatter = new ItemStackBuilder(Material.FILLED_MAP).title(ChatColor.GOLD, map.getName())
.title(ChatColor.DARK_GRAY, " - ").title(ChatColor.GRAY, I.t("Splatter Map"))
.title(ChatColor.DARK_GRAY, " - ")
.title(ChatColor.GRAY, I.t("{0} × {1}", map.getColumnCount(), map.getRowCount()))
.loreLine(ChatColor.GRAY, map.getId()).loreLine()
/// Title in a splatter map tooltip
.loreLine(ChatColor.BLUE, I.t("Item frames needed"))
/// Size of a map stored in a splatter map
.loreLine(ChatColor.GRAY,
I.t("{0} × {1} (total {2} frames)", map.getColumnCount(), map.getRowCount(),
map.getColumnCount() * map.getRowCount()))
.loreLine()
/// Title in a splatter map tooltip
.loreLine(ChatColor.BLUE, I.t("How to use this?"))
.longLore(
ChatColor.GRAY
+
2020-12-10 22:13:05 +01:00
I.t("Place empty item frames on a wall, enough to host the whole map."
+ " Then, right-click on the bottom-left frame with this map."),
2020-12-09 21:56:55 +01:00
40)
.loreLine()
.longLore(ChatColor.GRAY
+ I.t("Shift-click one of the placed maps to remove the whole poster in one shot."), 40)
2021-02-11 00:43:31 +01:00
.hideAllAttributes()
2020-12-09 21:56:55 +01:00
.craftItem();
final MapMeta meta = (MapMeta) splatter.getItemMeta();
meta.setMapId(map.getMapIdAt(0));
meta.setColor(Color.GREEN);
splatter.setItemMeta(meta);
return addSplatterAttribute(splatter);
}
/**
* To identify image on maps for the auto-splattering to work, we mark the
* items using an enchantment maps are not supposed to have (Mending).
2020-12-10 22:13:05 +01:00
*
2020-12-09 21:56:55 +01:00
* <p>
* Then we check if the map is enchanted at all to know if it's a splatter
* map. This ensure compatibility with old splatter maps from 3.x, where
* zLib's glow effect was used.
2020-12-10 22:13:05 +01:00
* </p>
2020-12-09 21:56:55 +01:00
* An AttributeModifier (using zLib's attributes system) is not used,
* because Minecraft (or Spigot) removes them from maps in 1.14+, so that
* wasn't stable enough (and the glowing effect of enchantments is
* prettier).
*
* @param itemStack The item stack to mark as a splatter map.
* @return The modified item stack. The instance may be different if the passed item stack is not a craft itemstack.
2020-12-09 21:56:55 +01:00
*/
2020-12-10 22:13:05 +01:00
public static ItemStack addSplatterAttribute(final ItemStack itemStack) {
2020-12-09 21:56:55 +01:00
try {
final NBTCompound nbt = NBT.fromItemStack(itemStack);
final NBTList enchantments = new NBTList();
final NBTCompound protection = new NBTCompound();
protection.put("id", "minecraft:mending");
protection.put("lvl", 1);
enchantments.add(protection);
nbt.put("Enchantments", enchantments);
return NBT.addToItemStack(itemStack, nbt, false);
} catch (NBTException | NMSException e) {
PluginLogger.error("Unable to set Splatter Map attribute on item", e);
return itemStack;
}
}
/**
* Checks if an item have the splatter attribute set (i.e. if the item is
* enchanted in any way).
*
* @param itemStack The item to check.
* @return True if the attribute was detected.
*/
2020-12-10 22:13:05 +01:00
public static boolean hasSplatterAttributes(ItemStack itemStack) {
2020-12-09 21:56:55 +01:00
try {
final NBTCompound nbt = NBT.fromItemStack(itemStack);
2020-12-10 22:13:05 +01:00
if (!nbt.containsKey("Enchantments")) {
return false;
}
2020-12-09 21:56:55 +01:00
final Object enchantments = nbt.get("Enchantments");
2020-12-10 22:13:05 +01:00
if (!(enchantments instanceof NBTList)) {
return false;
}
2020-12-09 21:56:55 +01:00
return !((NBTList) enchantments).isEmpty();
} catch (NMSException e) {
PluginLogger.error("Unable to get Splatter Map attribute on item", e);
return false;
}
}
/**
2021-02-07 13:18:39 +01:00
* Return true if it is a splatter map
2020-12-09 21:56:55 +01:00
*
* @param itemStack The item to check.
* @return True if is a splatter map
*/
2020-12-10 22:13:05 +01:00
public static boolean isSplatterMap(ItemStack itemStack) {
if (itemStack == null) {
return false;
}
2020-12-09 21:56:55 +01:00
return hasSplatterAttributes(itemStack) && MapManager.managesMap(itemStack);
}
2021-02-07 13:18:39 +01:00
/**
* Return true if it has a specified splatter map
*
* @param player The player to check.
* @param map The map to check.
* @return True if the player has this map
*/
2020-12-10 22:13:05 +01:00
public static boolean hasSplatterMap(Player player, PosterMap map) {
2020-12-09 21:56:55 +01:00
Inventory playerInventory = player.getInventory();
2020-12-09 21:56:55 +01:00
for (int i = 0; i < playerInventory.getSize(); ++i) {
ItemStack item = playerInventory.getItem(i);
2020-12-10 22:13:05 +01:00
if (isSplatterMap(item) && map.managesMap(item)) {
return true;
}
2020-12-09 21:56:55 +01:00
}
return false;
}
/**
* Place a splatter map
*
* @param startFrame Frame clicked by the player
* @param player Player placing map
* @return true if the map was correctly placed
*/
2020-12-10 22:13:05 +01:00
public static boolean placeSplatterMap(ItemFrame startFrame, Player player, PlayerInteractEntityEvent event) {
2020-12-09 21:56:55 +01:00
ImageMap map = MapManager.getMap(player.getInventory().getItemInMainHand());
2020-12-10 22:13:05 +01:00
if (!(map instanceof PosterMap)) {
return false;
}
2020-12-09 21:56:55 +01:00
PosterMap poster = (PosterMap) map;
PosterWall wall = new PosterWall();
if (startFrame.getFacing().equals(BlockFace.DOWN) || startFrame.getFacing().equals(BlockFace.UP)) {
// If it is on floor or ceiling
PosterOnASurface surface = new PosterOnASurface();
FlatLocation startLocation = new FlatLocation(startFrame.getLocation(), startFrame.getFacing());
FlatLocation endLocation = startLocation.clone().addH(poster.getColumnCount(), poster.getRowCount(),
WorldUtils.get4thOrientation(player.getLocation()));
surface.loc1 = startLocation;
surface.loc2 = endLocation;
if (!surface.isValid(player)) {
MessageSender.sendActionBarMessage(player,
2020-12-10 22:13:05 +01:00
I.t("{ce}There is not enough space to place this map ({0} × {1}).",
poster.getColumnCount(),
2020-12-09 21:56:55 +01:00
poster.getRowCount()));
return false;
}
int i = 0;
for (ItemFrame frame : surface.frames) {
BlockFace bf = WorldUtils.get4thOrientation(player.getLocation());
int id = poster.getMapIdAtReverseZ(i, bf, startFrame.getFacing());
Rotation rot = Rotation.NONE;
switch (frame.getFacing()) {
case UP:
break;
case DOWN:
rot = Rotation.FLIPPED;
break;
2020-12-10 22:13:05 +01:00
default:
//throw new IllegalStateException("Unexpected value: " + frame.getFacing());
2020-12-09 21:56:55 +01:00
}
2020-12-10 22:13:05 +01:00
//Rotation management relative to player rotation the default position is North,
// when on ceiling we flipped the rotation
RunTask.later(() -> {
frame.setItem(
new ItemStackBuilder(Material.FILLED_MAP).nbt(ImmutableMap.of("map", id)).craftItem());
}, 5L);
2020-12-10 22:13:05 +01:00
if (i == 0) {
//First map need to be rotate one time CounterClockwise
2020-12-09 21:56:55 +01:00
rot = rot.rotateCounterClockwise();
}
switch (bf) {
case NORTH:
if (frame.getFacing() == BlockFace.DOWN) {
rot = rot.rotateClockwise();
rot = rot.rotateClockwise();
}
frame.setRotation(rot);
break;
case EAST:
rot = rot.rotateClockwise();
frame.setRotation(rot);
break;
case SOUTH:
if (frame.getFacing() == BlockFace.UP) {
rot = rot.rotateClockwise();
rot = rot.rotateClockwise();
}
frame.setRotation(rot);
break;
case WEST:
rot = rot.rotateCounterClockwise();
frame.setRotation(rot);
break;
2020-12-10 22:13:05 +01:00
default:
throw new IllegalStateException("Unexpected value: " + bf);
2020-12-09 21:56:55 +01:00
}
MapInitEvent.initMap(id);
i++;
}
} else {
// If it is on a wall NSEW
FlatLocation startLocation = new FlatLocation(startFrame.getLocation(), startFrame.getFacing());
FlatLocation endLocation = startLocation.clone().add(poster.getColumnCount(), poster.getRowCount());
wall.loc1 = startLocation;
wall.loc2 = endLocation;
if (!wall.isValid()) {
MessageSender.sendActionBarMessage(player,
I.t("{ce}There is not enough space to place this map ({0} × {1}).", poster.getColumnCount(),
poster.getRowCount()));
return false;
}
int i = 0;
for (ItemFrame frame : wall.frames) {
int id = poster.getMapIdAtReverseY(i);
RunTask.later(() -> {
frame.setItem(
new ItemStackBuilder(Material.FILLED_MAP).nbt(ImmutableMap.of("map", id)).craftItem());
}, 5L);
2020-12-09 21:56:55 +01:00
//Force reset of rotation
frame.setRotation(Rotation.NONE);
MapInitEvent.initMap(id);
++i;
}
}
return true;
}
/**
* Remove splattermap
*
* @param startFrame Frame clicked by the player
* @param player The player removing the map
* @return
2020-12-10 22:13:05 +01:00
**/
public static PosterMap removeSplatterMap(ItemFrame startFrame, Player player) {
2020-12-09 21:56:55 +01:00
final ImageMap map = MapManager.getMap(startFrame.getItem());
2020-12-10 22:13:05 +01:00
if (!(map instanceof PosterMap)) {
return null;
}
2020-12-09 21:56:55 +01:00
PosterMap poster = (PosterMap) map;
2020-12-10 22:13:05 +01:00
if (!poster.hasColumnData()) {
return null;
}
2020-12-09 21:56:55 +01:00
FlatLocation loc = new FlatLocation(startFrame.getLocation(), startFrame.getFacing());
ItemFrame[] matchingFrames = null;
2020-12-09 21:56:55 +01:00
switch (startFrame.getFacing()) {
case UP:
case DOWN:
matchingFrames = PosterOnASurface.getMatchingMapFrames(poster, loc,
2020-12-09 21:56:55 +01:00
MapManager.getMapIdFromItemStack(startFrame.getItem()),
WorldUtils.get4thOrientation(player.getLocation()));//startFrame.getFacing());
break;
2020-12-09 21:56:55 +01:00
case NORTH:
case SOUTH:
case EAST:
case WEST:
matchingFrames = PosterWall.getMatchingMapFrames(poster, loc,
MapManager.getMapIdFromItemStack(startFrame.getItem()));
2020-12-10 22:13:05 +01:00
break;
default:
throw new IllegalStateException("Unexpected value: " + startFrame.getFacing());
2020-12-09 21:56:55 +01:00
}
2020-12-10 22:13:05 +01:00
if (matchingFrames == null) {
return null;
}
2020-12-09 21:56:55 +01:00
for (ItemFrame frame : matchingFrames) {
2020-12-10 22:13:05 +01:00
if (frame != null) {
frame.setItem(null);
}
2020-12-09 21:56:55 +01:00
}
2020-12-09 21:56:55 +01:00
return poster;
}
}