This commit is contained in:
Vlammar 2020-06-14 03:34:14 +02:00
commit 46cae5bb33
17 changed files with 1315 additions and 343 deletions

View File

@ -107,12 +107,18 @@ public final class ImageOnMap extends ZPlugin
Commands.registerShortcut("maptool", NewCommand.class, "tomap");
Commands.registerShortcut("maptool", ExploreCommand.class, "maps");
UpdateChecker.boot("imageonmap.26585");
if (PluginConfiguration.CHECK_FOR_UPDATES.get())
{
UpdateChecker.boot("imageonmap.26585");
}
final Metrics metrics = new Metrics(this);
if (PluginConfiguration.COLLECT_DATA.get())
{
final Metrics metrics = new Metrics(this);
metrics.addCustomChart(new Metrics.SingleLineChart("rendered-images", MapManager::getImagesCount));
metrics.addCustomChart(new Metrics.SingleLineChart("used-minecraft-maps", MapManager::getMapCount));
metrics.addCustomChart(new Metrics.SingleLineChart("rendered-images", MapManager::getImagesCount));
metrics.addCustomChart(new Metrics.SingleLineChart("used-minecraft-maps", MapManager::getMapCount));
}
}
@Override

View File

@ -31,10 +31,18 @@ public final class PluginConfiguration extends Configuration
static public ConfigurationItem<Locale> LANG = item("lang", Locale.class);
static public ConfigurationItem<Boolean> COLLECT_DATA = item("collect-data", true);
static public ConfigurationItem<Boolean> SAVE_FULL_IMAGE = item("save-full-image", true);
static public ConfigurationItem<Boolean> CHECK_FOR_UPDATES = item("check-for-updates", true);
static public ConfigurationItem<Integer> MAP_GLOBAL_LIMIT = item("map-global-limit", 0, "Limit-map-by-server");
static public ConfigurationItem<Integer> MAP_PLAYER_LIMIT = item("map-player-limit", 0, "Limit-map-by-player");
static public ConfigurationItem<Integer> MAP_SIZE_NOTOP_LIMIT = item("map-size-notop-limit", 0, "Size-limit-map-notop");
static public ConfigurationItem<Boolean> SAVE_FULL_IMAGE = item("save-full-image", true);
static public ConfigurationItem<Integer> LIMIT_SIZE_X = item("limit-map-size-x", 0);
static public ConfigurationItem<Integer> LIMIT_SIZE_Y = item("limit-map-size-y", 0);
}

View File

@ -75,34 +75,37 @@ public class NewCommand extends IoMCommand
default: throwInvalidArgument(I.t("Invalid Stretching mode.")); break;
}
}
ActionBar.sendPermanentMessage(player, ChatColor.DARK_GREEN + I.t("Rendering..."));
ImageRendererExecutor.render(url, scaling, player.getUniqueId(), width, height, new WorkerCallback<ImageMap>()
{
@Override
public void finished(ImageMap result)
{
ActionBar.removeMessage(player);
MessageSender.sendActionBarMessage(player, ChatColor.DARK_GREEN + I.t("Rendering finished!"));
try {
if (result.give(player) && (result instanceof PosterMap && !((PosterMap) result).hasColumnData()))
{
info(I.t("The rendered map was too big to fit in your inventory."));
info(I.t("Use '/maptool getremaining' to get the remaining maps."));
ActionBar.sendPermanentMessage(player, ChatColor.DARK_GREEN + I.t("Rendering..."));
ImageRendererExecutor.render(url, scaling, player.getUniqueId(), width, height, new WorkerCallback<ImageMap>() {
@Override
public void finished(ImageMap result) {
ActionBar.removeMessage(player);
MessageSender.sendActionBarMessage(player, ChatColor.DARK_GREEN + I.t("Rendering finished!"));
if (result.give(player) && (result instanceof PosterMap && !((PosterMap) result).hasColumnData())) {
info(I.t("The rendered map was too big to fit in your inventory."));
info(I.t("Use '/maptool getremaining' to get the remaining maps."));
}
}
}
@Override
public void errored(Throwable exception)
{
player.sendMessage(I.t("{ce}Map rendering failed: {0}", exception.getMessage()));
@Override
public void errored(Throwable exception) {
player.sendMessage(I.t("{ce}Map rendering failed: {0}", exception.getMessage()));
PluginLogger.warning("Rendering from {0} failed: {1}: {2}",
player.getName(),
exception.getClass().getCanonicalName(),
exception.getMessage());
}
});
PluginLogger.warning("Rendering from {0} failed: {1}: {2}",
player.getName(),
exception.getClass().getCanonicalName(),
exception.getMessage());
}
});
}
//Added to fix bug with rendering displaying after error
finally {
ActionBar.removeMessage(player);
}
}
@Override

View File

@ -35,6 +35,7 @@ public class ImageIOExecutor extends Worker
{
static public void loadImage(final File file, final Renderer mapRenderer)
{
submitQuery(new WorkerRunnable<Void>()
{
@Override

View File

@ -40,7 +40,6 @@ import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
@WorkerAttributes (name = "Image Renderer", queriesMainThread = true)
public class ImageRendererExecutor extends Worker
{

View File

@ -94,4 +94,4 @@ public class ImageUtils {
graphics.dispose();
return newImage;
}
}
}

View File

@ -38,7 +38,6 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.map.MapView;
import java.io.File;
public class MapInitEvent implements Listener
{
static public void init()
@ -113,8 +112,10 @@ public class MapInitEvent implements Listener
static public void initMap(MapView map)
{
if(map == null) return;
if(Renderer.isHandled(map)) return;
if(map == null) {
return;}
if(Renderer.isHandled(map)) {
return;}
File imageFile = ImageOnMap.getPlugin().getImageFile(map.getId());
if(imageFile.isFile())

View File

@ -93,7 +93,6 @@ public class Renderer extends MapRenderer
{
//Render only once to avoid overloading the server
if (image == null) return;
canvas.drawImage(0, 0, image);
image = null;
}

View File

@ -92,6 +92,7 @@ abstract public class MapManager
static public ImageMap createMap(PosterImage image, UUID playerUUID, int[] mapsIDs) throws MapManagerException
{
ImageMap newMap;
if(image.getImagesCount() == 1)
{
newMap = new SingleMap(playerUUID, mapsIDs[0]);

View File

@ -263,6 +263,7 @@ public class PlayerMapStore implements ConfigurationSerializable
try
{
getToolConfig().save(mapsFile);
}
catch (IOException ex)
{

View File

@ -18,155 +18,163 @@
package fr.moribus.imageonmap.map;
import org.bukkit.block.BlockFace;
import org.bukkit.configuration.InvalidConfigurationException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
public class PosterMap extends ImageMap
{
protected final int[] mapsIDs;
protected final int columnCount;
protected final int rowCount;
public PosterMap(UUID userUUID, int[] mapsIDs, String id, String name, int columnCount, int rowCount)
{
super(userUUID, Type.POSTER, id, name);
this.mapsIDs = mapsIDs;
this.columnCount = Math.max(columnCount, 0);
this.rowCount = Math.max(rowCount, 0);
}
public PosterMap(UUID userUUID, int[] mapsIDs, int columnCount, int rowCount)
{
this(userUUID, mapsIDs, null, null, columnCount, rowCount);
}
@Override
public int[] getMapsIDs()
{
return mapsIDs;
}
public class PosterMap extends ImageMap {
protected final int[] mapsIDs;
protected final int columnCount;
protected final int rowCount;
@Override
public boolean managesMap(int mapID)
{
for(int i = 0; i < mapsIDs.length; i++)
{
if(mapsIDs[i] == mapID) return true;
}
return false;
}
public PosterMap(UUID userUUID, int[] mapsIDs, String id, String name, int columnCount, int rowCount) {
super(userUUID, Type.POSTER, id, name);
this.mapsIDs = mapsIDs;
this.columnCount = Math.max(columnCount, 0);
this.rowCount = Math.max(rowCount, 0);
}
/* ====== Serialization methods ====== */
public PosterMap(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException
{
super(map, userUUID, Type.POSTER);
columnCount = getFieldValue(map, "columns");
rowCount = getFieldValue(map, "rows");
List<Integer> idList = getFieldValue(map, "mapsIDs");
mapsIDs = new int[idList.size()];
for(int i = 0, c = idList.size(); i < c; i++)
{
mapsIDs[i] = (int) idList.get(i);
}
}
@Override
protected void postSerialize(Map<String, Object> map)
{
map.put("columns", columnCount);
map.put("rows", rowCount);
map.put("mapsIDs", mapsIDs);
}
/* ====== Getters & Setters ====== */
/**
* Returns the amount of columns in the poster map
* @return The number of columns, or 0 if this data is missing
*/
public int getColumnCount()
{
return columnCount;
}
/**
* Returns the amount of rows in the poster map
* @return The number of rows, or 0 if this data is missing
*/
public int getRowCount()
{
return rowCount;
}
public int getColumnAt(int i)
{
if(columnCount == 0) return 0;
return (i % columnCount);
}
public int getRowAt(int i)
{
if(columnCount == 0) return 0;
return (i / columnCount);
}
public int getIndexAt(int col, int row)
{
return columnCount * row + col;
}
public PosterMap(UUID userUUID, int[] mapsIDs, int columnCount, int rowCount) {
this(userUUID, mapsIDs, null, null, columnCount, rowCount);
}
/**
* Returns the map id at the given column and line.
*
* @param x The x coordinate. Starts at 0.
* @param y The y coordinate. Starts at 0.
* @return The Minecraft map ID.
*
* @throws ArrayIndexOutOfBoundsException if the given coordinates are too big (out of the poster).
*/
public int getMapIdAt(int x, int y)
{
return mapsIDs[y * columnCount + x];
}
public int getMapIdAtReverseY(int index)
{
int x = index % (columnCount);
int y = index / (columnCount);
return getMapIdAt(x, rowCount - y - 1);
}
public int getMapIdAt(int index)
{
return mapsIDs[index];
}
public boolean hasColumnData()
{
return rowCount != 0 && columnCount != 0;
}
@Override
public int[] getMapsIDs() {
return mapsIDs;
}
@Override
public int getMapCount()
{
return mapsIDs.length;
}
public int getIndex(int mapID)
{
for(int i = 0; i < mapsIDs.length; i++)
{
if(mapsIDs[i] == mapID) return i;
}
throw new IllegalArgumentException("Invalid map ID");
}
@Override
public boolean managesMap(int mapID) {
for (int i = 0; i < mapsIDs.length; i++) {
if (mapsIDs[i] == mapID)
return true;
}
return false;
}
/* ====== Serialization methods ====== */
public PosterMap(Map<String, Object> map, UUID userUUID) throws InvalidConfigurationException {
super(map, userUUID, Type.POSTER);
columnCount = getFieldValue(map, "columns");
rowCount = getFieldValue(map, "rows");
List<Integer> idList = getFieldValue(map, "mapsIDs");
mapsIDs = new int[idList.size()];
for (int i = 0, c = idList.size(); i < c; i++) {
mapsIDs[i] = idList.get(i);
}
}
@Override
protected void postSerialize(Map<String, Object> map) {
map.put("columns", columnCount);
map.put("rows", rowCount);
map.put("mapsIDs", mapsIDs);
}
/* ====== Getters & Setters ====== */
/**
* Returns the amount of columns in the poster map
*
* @return The number of columns, or 0 if this data is missing
*/
public int getColumnCount() {
return columnCount;
}
/**
* Returns the amount of rows in the poster map
*
* @return The number of rows, or 0 if this data is missing
*/
public int getRowCount() {
return rowCount;
}
public int getColumnAt(int i) {
if (columnCount == 0)
return 0;
return (i % columnCount);
}
public int getRowAt(int i) {
if (columnCount == 0)
return 0;
return (i / columnCount);
}
public int getIndexAt(int col, int row) {
return columnCount * row + col;
}
/**
* Returns the map id at the given column and line.
*
* @param x
* The x coordinate. Starts at 0.
* @param y
* The y coordinate. Starts at 0.
* @return The Minecraft map ID.
*
* @throws ArrayIndexOutOfBoundsException
* if the given coordinates are too big (out of the poster).
*/
public int getMapIdAt(int x, int y) {
return mapsIDs[y * columnCount + x];
}
public int getMapIdAtReverseY(int index) {
int x = index % (columnCount);
int y = index / (columnCount);
return getMapIdAt(x, rowCount - y - 1);
}
public int getMapIdAtReverseZ(int index, BlockFace orientation, BlockFace bf) {
int x = 0, y = 0;
switch (bf) {
case UP:
x = index % (columnCount);
y = index / (columnCount);
break;
case DOWN:
x = (columnCount - 1) - index % (columnCount);
y = index / (columnCount);
break;
}
return getMapIdAt(x, rowCount - y - 1);
}
public int getMapIdAt(int index) {
return mapsIDs[index];
}
public boolean hasColumnData() {
return rowCount != 0 && columnCount != 0;
}
@Override
public int getMapCount() {
return mapsIDs.length;
}
public int getIndex(int mapID) {
for (int i = 0; i < mapsIDs.length; i++) {
if (mapsIDs[i] == mapID)
return i;
}
throw new IllegalArgumentException("Invalid map ID");
}
}

View File

@ -229,19 +229,20 @@ public class MapItemManager implements Listener
if (frame.getItem().getType() != Material.AIR) return;
if (!MapManager.managesMap(mapItem)) return;
event.setCancelled(true);
if (SplatterMapManager.hasSplatterAttributes(mapItem))
{
if (!SplatterMapManager.placeSplatterMap(frame, player))
return;
if (!SplatterMapManager.placeSplatterMap(frame, player,event)){
event.setCancelled(true); //In case of an error allow to cancel map placement
return;}
}
else
{
// If the item has a display name, bot not one from an anvil by the player, we remove it
// si it is not displayed on hover on the wall.
// If it is not displayed on hover on the wall.
if (mapItem.hasItemMeta() && mapItem.getItemMeta().hasDisplayName() && mapItem.getItemMeta().getDisplayName().startsWith("§r"))
{
final ItemStack frameItem = mapItem.clone();
final ItemMeta meta = frameItem.getItemMeta();
@ -251,9 +252,13 @@ public class MapItemManager implements Listener
frame.setItem(frameItem);
}
else frame.setItem(mapItem);
else{
frame.setItem(mapItem);
}
}
ItemUtils.consumeItem(player);
}
@ -264,7 +269,7 @@ public class MapItemManager implements Listener
if (player.isSneaking())
{
PosterMap poster = SplatterMapManager.removeSplatterMap(frame);
PosterMap poster = SplatterMapManager.removeSplatterMap(frame,player);
if (poster != null)
{
event.setCancelled(true);

View File

@ -0,0 +1,218 @@
/*
* Copyright (C) 2013 Moribus
* Copyright (C) 2015 ProkopyL <prokopylmc@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package fr.moribus.imageonmap.ui;
import fr.moribus.imageonmap.map.PosterMap;
import fr.zcraft.zlib.tools.world.FlatLocation;
import fr.zcraft.zlib.tools.world.WorldUtils;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Entity;
import org.bukkit.entity.ItemFrame;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
public class PosterOnASurface {
public FlatLocation loc1;
public FlatLocation loc2;
public ItemFrame[] frames;
public boolean isValid(Player p) {
ItemFrame curFrame;
FlatLocation l = loc1.clone();
BlockFace bf = WorldUtils.get4thOrientation(p.getLocation());
l.subtract(loc2);
int distX = Math.abs(l.getBlockX());
int distZ = Math.abs(l.getBlockZ());
frames = new ItemFrame[distX * distZ];
l = loc1.clone();
for (int x = 0; x < distX; x++) {
for (int z = 0; z < distZ; z++) {
curFrame = getEmptyFrameAt(l, l.getFacing());
if (curFrame == null)
return false;
frames[z * distX + x] = curFrame;
switch (bf) {
case NORTH:
case SOUTH:
l.addH(0, 1, bf);
break;
case EAST:
case WEST:
l.addH(1, 0, bf);
break;
}
}
switch (bf) {
case NORTH:
case SOUTH:
l.addH(1, -distZ, bf);
break;
case EAST:
case WEST:
l.addH(-distZ, 1, bf);
break;
}
}
return true;
}
public void expand() {
}
/**
* Return the list of map Frames associated with a specific map
* */
static public ItemFrame[] getMatchingMapFrames(PosterMap map, FlatLocation location, int mapId, BlockFace bf) {
int mapIndex = map.getIndex(mapId);
//int x = map.getColumnAt(mapIndex), y = map.getRowAt(mapIndex);
int x=0,y=0;
switch(bf){
case EAST:
case WEST:
y=map.getColumnCount()-1;
break;
case NORTH:
case SOUTH:
y=map.getRowCount()-1;
break;
}
return getMatchingMapFrames(map, location.clone().addH(x, y,bf),bf).clone();
}
static public ItemFrame[] getMatchingMapFrames(PosterMap map, FlatLocation location, BlockFace bf) {
ItemFrame[] frames = new ItemFrame[map.getMapCount()];
FlatLocation loc = location.clone();
int X=0;
int Y=0;
switch (bf){
case EAST:
case WEST:
/*X=map.getRowCount();
Y=map.getColumnCount();
break;*/
case NORTH:
case SOUTH:
Y=map.getRowCount();
X=map.getColumnCount();
break;
}
for (int y = 0; y < Y; ++y) {
for (int x = 0; x < X; ++x) {
int mapIndex = map.getIndexAt(x, y);
ItemFrame frame = getMapFrameAt(loc, map);
if (frame != null)
frames[mapIndex] = frame;
switch (bf){
case EAST:
case WEST:
loc.addH( 0,-1,bf);
break;
case NORTH:
case SOUTH:
loc.addH( 1,0,bf);
break;
}
}
switch (bf){
case EAST:
case WEST:
loc.addH( 1,map.getColumnCount(),bf);//test
break;
case NORTH:
case SOUTH:
loc.addH(-map.getColumnCount(), -1,bf);
break;
}
}
return frames;
}
static public ItemFrame getMapFrameAt(FlatLocation location, PosterMap map) {
Entity entities[] = location.getChunk().getEntities();
for (Entity entity : entities) {
if (!(entity instanceof ItemFrame))
continue;
if (!WorldUtils.blockEquals(location, entity.getLocation()))
continue;
ItemFrame frame = (ItemFrame) entity;
if (frame.getFacing() != location.getFacing())
continue;
ItemStack item = frame.getItem();
if (item.getType() != Material.FILLED_MAP)
continue;
if (!map.managesMap(item))
continue;
return frame;
}
return null;
}
static public ItemFrame getEmptyFrameAt(Location location, BlockFace facing) {
Entity entities[] = location.getChunk().getEntities();
for (Entity entity : entities) {
if (!(entity instanceof ItemFrame))
continue;
if (!WorldUtils.blockEquals(location, entity.getLocation()))
continue;
ItemFrame frame = (ItemFrame) entity;
if (frame.getFacing() != facing)
continue;
ItemStack item = frame.getItem();
if (item.getType() != Material.AIR)
continue;
return frame;
}
return null;
}
}

View File

@ -44,7 +44,7 @@ public class PosterWall
int distX = FlatLocation.flatBlockDistanceX(loc1, loc2);
int distY = FlatLocation.flatBlockDistanceY(loc1, loc2);
frames = new ItemFrame[distX * distY];
for(int x = 0; x < distX; x++)
@ -65,9 +65,7 @@ public class PosterWall
public void expand()
{
}
static public ItemFrame[] getMatchingMapFrames(PosterMap map, FlatLocation location, int mapId)

View File

@ -33,182 +33,306 @@ import fr.zcraft.zlib.tools.items.ItemStackBuilder;
import fr.zcraft.zlib.tools.reflection.NMSException;
import fr.zcraft.zlib.tools.text.MessageSender;
import fr.zcraft.zlib.tools.world.FlatLocation;
import fr.zcraft.zlib.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;
abstract public class SplatterMapManager
{
private SplatterMapManager() {}
static public ItemStack makeSplatterMap(PosterMap map)
{
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 + 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."), 40)
.loreLine()
.longLore(ChatColor.GRAY + I.t("Shift-click one of the placed maps to remove the whole poster in one shot."), 40)
.hideAttributes()
.craftItem();
abstract public class SplatterMapManager {
private SplatterMapManager() {
}
final MapMeta meta = (MapMeta) splatter.getItemMeta();
meta.setMapId(map.getMapIdAt(0));
meta.setColor(Color.GREEN);
splatter.setItemMeta(meta);
static public ItemStack makeSplatterMap(PosterMap map) {
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).
*
* 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.
*
* 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 item stack; that's why the instance is
* returned.
*/
static public ItemStack addSplatterAttribute(final ItemStack itemStack)
{
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);
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
+ 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."),
40)
.loreLine()
.longLore(ChatColor.GRAY
+ I.t("Shift-click one of the placed maps to remove the whole poster in one shot."), 40)
.hideAttributes().craftItem();
nbt.put("Enchantments", enchantments);
final MapMeta meta = (MapMeta) splatter.getItemMeta();
meta.setMapId(map.getMapIdAt(0));
meta.setColor(Color.GREEN);
splatter.setItemMeta(meta);
return NBT.addToItemStack(itemStack, nbt, false);
}
catch (NBTException | NMSException e)
{
PluginLogger.error("Unable to set Splatter Map attribute on item", e);
return itemStack;
}
}
return addSplatterAttribute(splatter);
}
/**
* 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.
*/
static public boolean hasSplatterAttributes(ItemStack itemStack)
{
try
{
final NBTCompound nbt = NBT.fromItemStack(itemStack);
if (!nbt.containsKey("Enchantments")) return false;
/**
* 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).
*
* 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.
*
* 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 item stack; that's why the
* instance is returned.
*/
static public ItemStack addSplatterAttribute(final ItemStack itemStack) {
try {
final NBTCompound nbt = NBT.fromItemStack(itemStack);
final NBTList enchantments = new NBTList();
final NBTCompound protection = new NBTCompound();
final Object enchantments = nbt.get("Enchantments");
if (!(enchantments instanceof NBTList)) return false;
protection.put("id", "minecraft:mending");
protection.put("lvl", 1);
enchantments.add(protection);
return !((NBTList) enchantments).isEmpty();
}
catch (NMSException e)
{
PluginLogger.error("Unable to get Splatter Map attribute on item", e);
return false;
}
}
static public boolean isSplatterMap(ItemStack itemStack)
{
return hasSplatterAttributes(itemStack) && MapManager.managesMap(itemStack);
}
static public boolean hasSplatterMap(Player player, PosterMap map)
{
Inventory playerInventory = player.getInventory();
for(int i = 0; i < playerInventory.getSize(); ++i)
{
ItemStack item = playerInventory.getItem(i);
if(isSplatterMap(item) && map.managesMap(item))
return true;
}
return false;
}
static public boolean placeSplatterMap(ItemFrame startFrame, Player player)
{
ImageMap map = MapManager.getMap(player.getInventory().getItemInMainHand());
nbt.put("Enchantments", enchantments);
if(!(map instanceof PosterMap)) return false;
PosterMap poster = (PosterMap) map;
return NBT.addToItemStack(itemStack, nbt, false);
} catch (NBTException | NMSException e) {
PluginLogger.error("Unable to set Splatter Map attribute on item", e);
return itemStack;
}
}
FlatLocation startLocation = new FlatLocation(startFrame.getLocation(), startFrame.getFacing());
FlatLocation endLocation = startLocation.clone().add(poster.getColumnCount(), poster.getRowCount());
PosterWall wall = new PosterWall();
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);
frame.setItem(new ItemStackBuilder(Material.FILLED_MAP).nbt(ImmutableMap.of("map", id)).craftItem());
MapInitEvent.initMap(id);
++i;
}
return true;
}
static public PosterMap removeSplatterMap(ItemFrame startFrame)
{
final ImageMap map = MapManager.getMap(startFrame.getItem());
if(!(map instanceof PosterMap)) return null;
PosterMap poster = (PosterMap) map;
if(!poster.hasColumnData()) return null;
FlatLocation loc = new FlatLocation(startFrame.getLocation(), startFrame.getFacing());
ItemFrame[] matchingFrames = PosterWall.getMatchingMapFrames(poster, loc, MapManager.getMapIdFromItemStack(startFrame.getItem()));
if(matchingFrames == null) return null;
for(ItemFrame frame : matchingFrames)
{
if(frame != null) frame.setItem(null);
}
return poster;
}
/**
* 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.
*/
static public boolean hasSplatterAttributes(ItemStack itemStack) {
try {
final NBTCompound nbt = NBT.fromItemStack(itemStack);
if (!nbt.containsKey("Enchantments"))
return false;
final Object enchantments = nbt.get("Enchantments");
if (!(enchantments instanceof NBTList))
return false;
return !((NBTList) enchantments).isEmpty();
} catch (NMSException e) {
PluginLogger.error("Unable to get Splatter Map attribute on item", e);
return false;
}
}
/**
* Return true if it is a platter map
*
* @param itemStack
* The item to check.
* @return True if is a splatter map
*/
static public boolean isSplatterMap(ItemStack itemStack) {
if(itemStack==null) return false;
return hasSplatterAttributes(itemStack) && MapManager.managesMap(itemStack);
}
//TODO doc a faire
static public boolean hasSplatterMap(Player player, PosterMap map) {
Inventory playerInventory = player.getInventory();
for (int i = 0; i < playerInventory.getSize(); ++i) {
ItemStack item = playerInventory.getItem(i);
if (isSplatterMap(item) && map.managesMap(item))
return true;
}
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
*/
static public boolean placeSplatterMap(ItemFrame startFrame, Player player, PlayerInteractEntityEvent event) {
ImageMap map = MapManager.getMap(player.getInventory().getItemInMainHand());
if (!(map instanceof PosterMap))
return false;
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,
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 : 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;
}
//Rotation management relative to player rotation the default position is North, when on ceiling we flipped the rotation
frame.setItem(new ItemStackBuilder(Material.FILLED_MAP).nbt(ImmutableMap.of("map", id)).craftItem());
if(i==0){//First map need to be rotate one time CounterClockwise
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;
}
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);
frame.setItem(new ItemStackBuilder(Material.FILLED_MAP).nbt(ImmutableMap.of("map", id)).craftItem());
//Force reset of rotation
if(i==0){//First map need to be rotate one time Clockwise
frame.setRotation(Rotation.NONE.rotateCounterClockwise());
}
else{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
*/
static public PosterMap removeSplatterMap(ItemFrame startFrame, Player player) {
final ImageMap map = MapManager.getMap(startFrame.getItem());
if (!(map instanceof PosterMap))
return null;
PosterMap poster = (PosterMap) map;
if (!poster.hasColumnData())
return null;
FlatLocation loc = new FlatLocation(startFrame.getLocation(), startFrame.getFacing());
ItemFrame[] matchingFrames=null;
switch(startFrame.getFacing()){
case UP:
case DOWN:
matchingFrames = PosterOnASurface.getMatchingMapFrames(poster, loc,
MapManager.getMapIdFromItemStack(startFrame.getItem()),WorldUtils.get4thOrientation(player.getLocation()));//startFrame.getFacing());
break;
case NORTH:
case SOUTH:
case EAST:
case WEST:
matchingFrames = PosterWall.getMatchingMapFrames(poster, loc,
MapManager.getMapIdFromItemStack(startFrame.getItem()));
}
if (matchingFrames == null)
return null;
for (ItemFrame frame : matchingFrames) {
if (frame != null)
frame.setItem(null);
}
return poster;
}
}

View File

@ -18,6 +18,11 @@ map-global-limit: 0
map-player-limit: 0
#Limit to the size of map non operator can render
#The value is the number of map used for the image for instance if you make a map 10 X 12 you will get 120 maps
map-size-notop-limit: 256
#Maximum size in pixels for an image to be. 0 is unlimited.
limit-map-size-x: 0
limit-map-size-y: 0

View File

@ -0,0 +1,595 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
msgid ""
msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-07-10 03:07+0200\n"
"PO-Revision-Date: 2016-07-10 03:08+0200\n"
"Last-Translator: danechek\n"
"Language-Team: \n"
"Language: ru_RU\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n < 1 || n > 1);\n"
"X-Generator: Poedit 1.8.7.1\n"
#: src/main/java/fr/moribus/imageonmap/commands/IoMCommand.java:40
msgid "You need to give a map name."
msgstr "Вам нужно дать имя карте."
#: src/main/java/fr/moribus/imageonmap/commands/IoMCommand.java:57
#: src/main/java/fr/moribus/imageonmap/commands/maptool/DeleteNoConfirmCommand.java:51
msgid "This map does not exist."
msgstr "Такая карта не существует."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/DeleteConfirmCommand.java:39
msgid "You are going to delete"
msgstr "Вы собираетесь удалить"
#: src/main/java/fr/moribus/imageonmap/commands/maptool/DeleteConfirmCommand.java:42
msgid "Are you sure ? "
msgstr "Вы уверены? "
#: src/main/java/fr/moribus/imageonmap/commands/maptool/DeleteConfirmCommand.java:44
msgid "[Confirm]"
msgstr "[Подтвердить]"
#: src/main/java/fr/moribus/imageonmap/commands/maptool/DeleteConfirmCommand.java:46
msgid "{red}This map will be deleted {bold}forever{red}!"
msgstr "{red}Эта карта будет удалена {bold}навсегда{red}!"
#: src/main/java/fr/moribus/imageonmap/commands/maptool/DeleteNoConfirmCommand.java:46
msgid "Map successfully deleted."
msgstr "Карта успешно удалена."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/GetCommand.java:38
msgid "The requested map was too big to fit in your inventory."
msgstr "Запрашиваемая карта слишком большая для вашего инвентаря."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/GetCommand.java:39
#: src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java:71
msgid "Use '/maptool getremaining' to get the remaining maps."
msgstr "Используйте '/maptool getremaining', чтобы получить остальные карты."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/GetRemainingCommand.java:38
msgid "You have no remaining map."
msgstr "У вас нет остальных карт."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/GetRemainingCommand.java:46
msgid ""
"Your inventory is full! Make some space before requesting the remaining maps."
msgstr ""
"Ваш инвентарь заполнен! Освободите место перед тем, как запросить остальные карты."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/GetRemainingCommand.java:50
#, java-format
msgid "There is {0} map remaining."
msgid_plural "There are {0} maps remaining."
msgstr[0] "Осталась {0} карта."
msgstr[1] "Осталось {0} карт."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/ListCommand.java:49
msgid "No map found."
msgstr "Карта не найдена."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/ListCommand.java:53
msgid "{white}{bold}{0} map found."
msgid_plural "{white}{bold}{0} maps found."
msgstr[0] "{white}{bold}{0} карта найдена."
msgstr[1] "{white}{bold}{0} карты найдены."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/ListCommand.java:79
msgid "{white}Click{gray} to get this map"
msgstr "{white}Кликните{gray}, чтобы получить эту карту"
#: src/main/java/fr/moribus/imageonmap/commands/maptool/MigrateCommand.java:36
msgid "A migration process is already running. Check console for details."
msgstr "Процесс миграции уже запущен. Проверьте консоль, чтобы уточнить детали."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/MigrateCommand.java:40
msgid "Migration started. See console for details."
msgstr "Миграция начата. Смотрите консоль, чтобы уточнить детали."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java:44
msgid "You must give an URL to take the image from."
msgstr "Вы должны дать URL, откуда будет взято изображение."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java:52
msgid "Invalid URL."
msgstr "Неверный URL"
#: src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java:61
msgid "Rendering..."
msgstr "Прорисовка..."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java:67
msgid "Rendering finished!"
msgstr "Прорисовка завершена!"
#: src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java:70
msgid "The rendered map was too big to fit in your inventory."
msgstr "Прорисованная карта слишком большая для вашего инвентаря."
#: src/main/java/fr/moribus/imageonmap/commands/maptool/NewCommand.java:78
msgid "{ce}Map rendering failed: {0}"
msgstr "{ce}Ошибока прорисовки карты: {0}"
#. The title of the map deletion GUI. {0}: map name.
#: src/main/java/fr/moribus/imageonmap/gui/ConfirmDeleteMapGui.java:102
msgid "{0} » {black}Confirm deletion"
msgstr "{0} » {black}Подтвердить удаление"
#. The title of the map deletion item
#: src/main/java/fr/moribus/imageonmap/gui/ConfirmDeleteMapGui.java:110
msgid "{red}You're about to destroy this map..."
msgstr "{red}Вы собираетесь уничтожить эту карту..."
#. The end, in the lore, of a title starting with “You're about to destroy this map...”.
#: src/main/java/fr/moribus/imageonmap/gui/ConfirmDeleteMapGui.java:112
msgid "{red}...{italic}forever{red}."
msgstr "{red}...{italic}навсегда{red}."
#: src/main/java/fr/moribus/imageonmap/gui/ConfirmDeleteMapGui.java:114
msgid "{gray}Name: {white}{0}"
msgstr "{gray}Имя: {white}{0}"
#: src/main/java/fr/moribus/imageonmap/gui/ConfirmDeleteMapGui.java:115
msgid "{gray}Map ID: {white}{0}"
msgstr "{gray}ID карты: {white}{0}"
#: src/main/java/fr/moribus/imageonmap/gui/ConfirmDeleteMapGui.java:116
msgid "{gray}Maps inside: {white}{0}"
msgstr "{gray}Карты внутри: {white}{0}"
#: src/main/java/fr/moribus/imageonmap/gui/ConfirmDeleteMapGui.java:178
msgid "{gray}Map successfully deleted."
msgstr "{gray}Карта успешно удалена."
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:54
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:71
msgid "{green}Map part"
msgstr "{green}Часть карты"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:55
msgid "{gray}Column: {white}{0}"
msgstr "{gray}Столбец: {white}{0}"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:56
msgid "{gray}Row: {white}{0}"
msgstr "{gray}Строка: {white}{0}"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:58
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:74
msgid "{gray}» {white}Click{gray} to get only this part"
msgstr "{gray}» {white}Кликните{gray}, чтобы получить эту часть"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:72
msgid "{gray}Part: {white}{0}"
msgstr "{gray}Часть: {white}{0}"
#. Title of the map details GUI
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:114
msgid "Your maps » {black}{0}"
msgstr "Ваши карты » {black}{0}"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:136
msgid "{blue}Rename this image"
msgstr "{blue}Переименуйте это изображение"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:137
msgid ""
"{gray}Click here to rename this image; this is used for your own "
"organization."
msgstr ""
"{gray}Кликните сюда, чтобы переименовать это изображение; это используется для вашей собственной "
"организации."
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:141
msgid "{red}Delete this image"
msgstr "{red}Удалить это изображение"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:142
msgid ""
"{gray}Deletes this map {white}forever{gray}. This action cannot be undone!"
msgstr ""
"{gray}Удаляет эту карту {white}навсегда{gray}. Это действие не может быть завершено!"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:144
msgid "{gray}You will be asked to confirm your choice if you click here."
msgstr "{gray}Вам будет предложено подтвердить свой выбор, если вы кликните здесь."
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:156
msgid "{green}« Back"
msgstr "{green}« Назад"
#: src/main/java/fr/moribus/imageonmap/gui/MapDetailGui.java:157
msgid "{gray}Go back to the list."
msgstr "{gray}Вернуться к списку."
#. Displayed subtitle description of a single map on the list GUI
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:44
msgid "{white}Single map"
msgstr "{white}Одиночная карта"
#. Displayed subtitle description of a poster map on the list GUI (columns × rows in english)
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:52
msgid "{white}Poster map ({0} × {1})"
msgstr "{white}Постер ({0} × {1})"
#. Displayed subtitle description of a poster map without column data on the list GUI
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:57
msgid "{white}Poster map ({0} parts)"
msgstr "{white}Постер ({0} parts)"
#. Displayed title of a map on the list GUI
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:62
msgid "{green}{bold}{0}"
msgstr "{green}{bold}{0}"
#. Map ID displayed in the tooltip of a map on the list GUI
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:67
msgid "{gray}Map ID: {0}"
msgstr "{gray}ID карты: {0}"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:69
msgid "{gray}» {white}Left-click{gray} to get this map"
msgstr "{gray}» {white}ЛКМ{gray} для получения этой карты"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:70
msgid "{gray}» {white}Right-click{gray} for details and options"
msgstr "{gray}» {white}ПКМ{gray} для деталей и опций"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:79
msgid "{red}You don't have any map."
msgstr "{red}У вас нет карт."
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:80
msgid ""
"{gray}Get started by creating a new one using {white}/tomap <URL> [resize]"
"{gray}!"
msgstr ""
"{gray}Начните создание новой с помощью {white}/tomap <URL> [resize]"
"{gray}!"
#. The maps list GUI title
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:119
msgid "{black}Your maps {reset}({0})"
msgstr "{black}Ваши карты {reset}({0})"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:148
msgid "{blue}Usage statistics"
msgstr "{blue}Статистика использования"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:150
msgid "{white}{0}{gray} image rendered"
msgid_plural "{white}{0}{gray} images rendered"
msgstr[0] "{white}{0}{gray} изображение прорисовано"
msgstr[1] "{white}{0}{gray} изображения прорисованы"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:151
msgid "{white}{0}{gray} Minecraft map used"
msgid_plural "{white}{0}{gray} Minecraft maps used"
msgstr[0] "{white}{0}{gray} Minecraft карта использована"
msgstr[1] "{white}{0}{gray} Minecraft карты использованы"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:156
msgid "{blue}Minecraft maps limits"
msgstr "{blue}Minecraft лимит карт"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:158
msgid "{gray}Server-wide limit: {white}unlimited"
msgstr "{gray}Лимит на сервере: {white}неограничено"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:159
msgid "{gray}Server-wide limit: {white}{0}"
msgstr "{gray}Лимит на сервере: {white}{0}"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:161
msgid "{gray}Per-player limit: {white}unlimited"
msgstr "{gray}Лимит для игрокаt: {white}неограничено"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:162
msgid "{gray}Per-player limit: {white}{0}"
msgstr "{gray}Лимит для игрока: {white}{0}"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:164
msgid "{white}{0} %{gray} of your quota used"
msgstr "{white}{0} %{gray} часть, используемая вами"
#: src/main/java/fr/moribus/imageonmap/gui/MapListGui.java:165
msgid "{white}{0}{gray} map left"
msgid_plural "{white}{0}{gray} maps left"
msgstr[0] "{white}{0}{gray} карта осталась"
msgstr[1] "{white}{0}{gray} карт осталось"
#: src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java:73
#, java-format
msgid "HTTP error : {0} {1}"
msgstr "HTTP ошибка : {0} {1}"
#: src/main/java/fr/moribus/imageonmap/image/ImageRendererExecutor.java:79
msgid "The given URL is not a valid image"
msgstr "Указанный URL имеет неверный формат изображения"
#. The default display name of a map
#: src/main/java/fr/moribus/imageonmap/map/ImageMap.java:44
msgid "Map"
msgstr "Карта"
#: src/main/java/fr/moribus/imageonmap/map/MapManagerException.java:29
#, java-format
msgid "You have too many maps (maximum : {0})."
msgstr "Вы имеете слишком много карт (максимум : {0})."
#: src/main/java/fr/moribus/imageonmap/map/MapManagerException.java:30
msgid "The server ImageOnMap limit has been reached."
msgstr "Лимит карт на сервере достигнут."
#: src/main/java/fr/moribus/imageonmap/map/MapManagerException.java:31
msgid "The given map does not exist."
msgstr "Данная карта не существует."
#: src/main/java/fr/moribus/imageonmap/migration/MigratorExecutor.java:34
msgid "Migration is already running."
msgstr "Миграция уже запущена."
#: src/main/java/fr/moribus/imageonmap/migration/MigratorExecutor.java:50
msgid "Waiting for migration to finish..."
msgstr "Подождите окончания миграции..."
#: src/main/java/fr/moribus/imageonmap/migration/MigratorExecutor.java:58
msgid ""
"Migration thread has been interrupted while waiting to finish. It may not "
"have ended correctly."
msgstr ""
"Поток миграции был прерван во время ожидания завершения. Может быть "
"не завершено корректно."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:176
msgid "Error while preparing migration"
msgstr "Ошибка во время подготовки миграции"
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:177
msgid "Aborting migration. No change has been made."
msgstr "Прерываю миграции. Изменения не будут применены."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:189
msgid "Error while migrating"
msgstr "Ошибка во время миграции"
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:190
msgid "Aborting migration. Some changes may already have been made."
msgstr "Прерываю миграции. Некоторые изменения могут быть применены."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:191
msgid ""
"Before trying to migrate again, you must recover player files from the "
"backups, and then move the backups away from the plugin directory to avoid "
"overwriting them."
msgstr ""
"Перед попыткой повторной миграции, вы должны восстановить файлы игроков из "
"бэкапов, затем переместить резервные копии из каталога плагинов, чтобы избежать "
"их перезаписи."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:203
msgid "Looking for configuration files to migrate..."
msgstr "Ищем файлы для конфигурации миграции..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:206
#, java-format
msgid "Detected former posters file {0}"
msgstr "Обнаружен файл прошлых постеров{0}"
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:209
#, java-format
msgid "Detected former maps file {0}"
msgstr "Обнаружен файл прошлых постеров {0}"
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:213
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:416
msgid "There is nothing to migrate. Stopping."
msgstr "Нечему мигрировать. Останавливаю."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:218
msgid "Done."
msgstr "Завершено."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:232
msgid "Backup directories already exists."
msgstr "Бэкап каталоги уже существуют."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:233
msgid ""
"This means that a migration has already been done, or may not have ended "
"well."
msgstr ""
"Это означает, что миграция уже выполнена, или, возможно, не закончилась"
"хорошо."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:234
msgid ""
"To start a new migration, you must move away the backup directories so they "
"are not overwritten."
msgstr ""
"Чтобы начать новую миграцию, вы должны переместить каталоги бэкапов, чтобы "
"они не перезаписались."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:247
msgid "Backing up map data before migrating..."
msgstr "Резервное копирование карт перед миграцией..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:271
msgid "Backup complete."
msgstr "Бэкап завершён."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:352
msgid "Fetching UUIDs from Mojang..."
msgstr "Получаем UUIDs от Mojang..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:359
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:387
msgid "An error occurred while fetching the UUIDs from Mojang"
msgstr "Произошла ошибка при получении UUIDs от Mojang"
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:364
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:392
msgid "The migration worker has been interrupted"
msgstr "Миграционный работник был прерван"
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:367
#, java-format
msgid "Fetching done. {0} UUID have been retrieved."
msgid_plural "Fetching done. {0} UUIDs have been retrieved."
msgstr[0] "Получение завершено. {0} UUID был получен."
msgstr[1] "Получение завершено. {0} UUIDs были получены."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:378
#, java-format
msgid "Mojang did not find UUIDs for {0} player at the current time."
msgid_plural "Mojang did not find UUIDs for {0} players at the current time."
msgstr[0] "Mojang не нашёл UUIDs для {0} игрока на текущее время."
msgstr[1] "Mojang не нашёл UUIDs для {0} игроков на текущее время."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:379
msgid ""
"The Mojang servers limit requests rate at one per second, this may take some "
"time..."
msgstr ""
"Серверы Mojang имеют ограничение запросов, это может занять некоторое "
"время..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:398
#, java-format
msgid "Mojang did not find player data for {0} player"
msgid_plural "Mojang did not find player data for {0} players"
msgstr[0] "Mojang не нашёл данных для игрока {0}"
msgstr[1] "Mojang не нашёл данных для игроков {0}"
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:400
msgid "The following players do not exist or do not have paid accounts :"
msgstr "Следующие игроки не существуют или не имеют платных аккаунтов:"
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:415
msgid "Mojang could not find any of the registered players."
msgstr "Mojang не удалось найти существующих игроков."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:425
msgid "Merging map data..."
msgstr "Объединяю данные карт..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:475
#, java-format
msgid "{0} registered minecraft map is missing from the save."
msgid_plural "{0} registered minecraft maps are missing from the save."
msgstr[0] "{0} зарегистрированная minecraft карта отсутствует в сохранении."
msgstr[1] "{0} зарегистрированные minecraft карты отсутствуют в сохранении."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:476
msgid ""
"These maps will not be migrated, but this could mean the save has been "
"altered or corrupted."
msgstr ""
"Эти карты не будут перенесены, но это может означать, что сохранение было "
"изменено или повреждено."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:477
#, java-format
msgid "The following maps are missing : {0} "
msgstr "Следующие карты отсутствуют : {0} "
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:483
msgid "Saving changes..."
msgstr "Сохраняю изменения..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:489
msgid "Cleaning up old data files..."
msgstr "Очищаю устаревшие данные файлов..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:496
msgid "Deleting old map data file..."
msgstr "Удаляю устаревший файл карты..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:501
#, java-format
msgid "{0} map could not be migrated."
msgid_plural "{0} maps could not be migrated."
msgstr[0] "{0} карта не может быть перенесена."
msgstr[1] "{0} карты не могут быть перенесены."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:519
msgid "Deleting old poster data file..."
msgstr "Удаление устаревшего файла данных постера..."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:524
#, java-format
msgid "{0} poster could not be migrated."
msgid_plural "{0} posters could not be migrated."
msgstr[0] "{0} постер не может быть перенесён."
msgstr[1] "{0} постеры не могут быть перенесены."
#: src/main/java/fr/moribus/imageonmap/migration/V3Migrator.java:537
msgid "Data that has not been migrated will be kept in the old data files."
msgstr "Данные, которые не были перенесены, будут сохранены в старых файлах данных."
#. The name of a map item given to a player, if splatter maps are not used. 0 = map name; 1 = index.
#: src/main/java/fr/moribus/imageonmap/ui/MapItemManager.java:139
#: src/main/java/fr/moribus/imageonmap/ui/MapItemManager.java:215
#, java-format
msgid "{0} (part {1})"
msgstr "{0} (часть {1})"
#. The name of a map item given to a player, if splatter maps are not used. 0 = map name; 1 = row; 2 = column.
#: src/main/java/fr/moribus/imageonmap/ui/MapItemManager.java:145
#: src/main/java/fr/moribus/imageonmap/ui/MapItemManager.java:213
#, java-format
msgid "{0} (row {1}, column {2})"
msgstr "{0} (строка {1}, столбец {2})"
#: src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java:44
msgid "Splatter Map"
msgstr "Размещаю карту"
#. Title in a splatter map tooltip
#: src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java:48
msgid "Item frames needed"
msgstr "Нужны рамки для предметов"
#. Size of a map stored in a splatter map
#: src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java:56
#, java-format
msgid "{0} × {1}"
msgstr "{0} × {1}"
#. Size of a map stored in a splatter map, including the total frames count
#: src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java:62
#, java-format
msgid "{0} × {1} (total {2} frames)"
msgstr "{0} × {1} (всего {2} рамок)"
#. Title in a splatter map tooltip
#: src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java:53
msgid "How to use this?"
msgstr "Как использовать это?"
#: src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java:54
msgid ""
"Place empty item frames on a wall, enough to host the whole map. Then, right-"
"click on the bottom-left frame with this map."
msgstr ""
"Поместите пустые рамки на стену, столько, чтобы разместить всю карту. Теперь, ПКМ по"
"нижней левой рамке этой картой."
#: src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java:56
msgid ""
"Shift-click one of the placed maps to remove the whole poster in one shot."
msgstr ""
"Shift-клик по одной из размещенных карт для удаления всего постера за один раз."
#: src/main/java/fr/moribus/imageonmap/ui/SplatterMapManager.java:101
msgid "{ce}There is not enough space to place this map ({0} × {1})."
msgstr "{ce}Здесь недостаточно места для размещения этой картй ({0} × {1})."