/*
* Copyright or © or Copr. Amaury Carrade (2014 - 2016)
*
* http://amaury.carrade.eu
*
* This software is governed by the CeCILL-B 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-B
* 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-B license and that you accept its terms.
*/
package eu.carrade.amaury.UHCReloaded.borders;
import eu.carrade.amaury.UHCReloaded.UHCReloaded;
import eu.carrade.amaury.UHCReloaded.UHConfig;
import eu.carrade.amaury.UHCReloaded.borders.exceptions.CannotGenerateWallsException;
import eu.carrade.amaury.UHCReloaded.borders.generators.WallGenerator;
import eu.carrade.amaury.UHCReloaded.borders.worldborders.WorldBorder;
import eu.carrade.amaury.UHCReloaded.task.BorderWarningTask;
import eu.carrade.amaury.UHCReloaded.timers.UHTimer;
import eu.carrade.amaury.UHCReloaded.utils.UHUtils;
import fr.zcraft.zlib.components.i18n.I;
import fr.zcraft.zlib.tools.PluginLogger;
import fr.zcraft.zlib.tools.runners.RunTask;
import fr.zcraft.zlib.tools.text.Titles;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.World.Environment;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable;
import java.util.HashSet;
import java.util.Set;
public class BorderManager
{
private final boolean BORDER_SHRINKING;
private final long BORDER_SHRINKING_STARTS_AFTER;
private final long BORDER_SHRINKING_DURATION;
private final double BORDER_SHRINKING_FINAL_SIZE;
private UHCReloaded p = null;
private WorldBorder border = null;
private Integer warningSize = 0;
private BukkitRunnable warningTask = null;
private Boolean warningFinalTimeEnabled = false;
private String warningTimerName = null;
private CommandSender warningSender = null;
private MapShape mapShape = null;
public BorderManager(UHCReloaded plugin)
{
p = plugin;
/// The name of the warning timer displaying the time left before the next border
warningTimerName = I.t("Border shrinking");
mapShape = UHConfig.MAP.SHAPE.get();
if (mapShape == null)
{
PluginLogger.warning("Invalid shape '" + UHConfig.MAP.SHAPE.get() + "'; using 'squared' instead.");
mapShape = MapShape.SQUARED;
}
World world = UHUtils.getOverworld();
if (world == null)
{
world = Bukkit.getWorlds().get(0);
PluginLogger.warning("Cannot find overworld! Using the world '{0}' instead (environment: {1}).", world.getName(), world.getEnvironment());
}
border = WorldBorder.getInstance(world, UHConfig.MAP.BORDER.MOTOR.get(), mapShape);
border.setShape(mapShape);
border.setCenter(world.getSpawnLocation());
border.setDiameter(UHConfig.MAP.SIZE.get());
border.init();
PluginLogger.info("Using {0} to set the world border.", border.getClass().getSimpleName());
BORDER_SHRINKING = UHConfig.MAP.BORDER.SHRINKING.ENABLED.get();
BORDER_SHRINKING_STARTS_AFTER = UHUtils.string2Time(UHConfig.MAP.BORDER.SHRINKING.STARTS_AFTER.get(), 30*60); // Seconds
BORDER_SHRINKING_DURATION = UHUtils.string2Time(UHConfig.MAP.BORDER.SHRINKING.SHRINKS_DURING.get(), 60*60*2); // Same
BORDER_SHRINKING_FINAL_SIZE = UHConfig.MAP.BORDER.SHRINKING.DIAMETER_AFTER_SHRINK.get();
}
/**
* Sets the shape of the map. Updates the WorldBorder too.
*
* @param shape The shape.
*/
public void setMapShape(MapShape shape)
{
this.mapShape = shape;
border.setShape(shape);
}
/**
* Returns the current shape of the map.
*
* @return The shape.
*/
public MapShape getMapShape()
{
return mapShape;
}
/**
* @return The WorldBorder proxy to set the border in-game.
*/
public WorldBorder getBorderProxy()
{
return border;
}
/**
* Checks if a given location is inside the border with the given diameter.
* The check is performed for a circular or squared border, following the configuration.
*
* @param location The location to check.
* @param diameter The diameter of the checked border.
*
* @return {@code true} if inside.
*/
public boolean isInsideBorder(Location location, double diameter)
{
// The nether/end are not limited.
return !location.getWorld().getEnvironment().equals(Environment.NORMAL) || mapShape.getShape().isInsideBorder(location, diameter, location.getWorld().getSpawnLocation());
}
/**
* Checks if a given location is inside the border with the current diameter.
* The check is performed for a circular or squared border, following the configuration.
*
* @param location The location to check.
* @return {@code true} if inside.
*/
public boolean isInsideBorder(Location location)
{
return this.isInsideBorder(location, getCurrentBorderDiameter());
}
/**
* Returns the distance from the location to the border, if the location is outside this border.
* If it is inside, or in another world, returns 0.
*
* @param location The location to check.
* @param diameter The diameter of the checked border.
*
* @return The distance, or 0 if the player is either inside the border or not in the world.
*/
public double getDistanceToBorder(Location location, double diameter)
{
return mapShape.getShape().getDistanceToBorder(location, diameter, location.getWorld().getSpawnLocation());
}
/**
* Returns a list of the players outside a border with the given diameter.
* The check is performed for a circular or squared border, following the configuration.
*
* @param diameter The diameter of the checked border.
* @return A list of players out of the given diameter.
*/
public Set<Player> getPlayersOutside(int diameter)
{
HashSet<Player> playersOutside = new HashSet<Player>();
for (final Player player : p.getGameManager().getOnlineAlivePlayers())
{
if (!isInsideBorder(player.getLocation(), diameter))
{
playersOutside.add(player);
}
}
return playersOutside;
}
/**
* Returns the size of the future border, used in the warning messages sent to the
* players out of this future border.
*
* @return the future border diameter.
*/
public int getWarningSize()
{
return this.warningSize;
}
/**
* @return true if there is currently a warning with a time left displayed.
*/
public boolean getWarningFinalTimeEnabled()
{
return this.warningFinalTimeEnabled;
}
/**
* @return the sender of the last warning configured.
*/
public CommandSender getWarningSender()
{
return this.warningSender;
}
/**
* Sets the size of the future border, used in the warning messages sent to the
* players out of this future border.
*
* This also starts the display of the warning messages, every 90 seconds by default
* (configurable, see config.yml, map.border.warningInterval).
*
* If timeLeft is not null, the time available for the players to go inside the future
* border is displayed in the warning message.
*
* @param diameter The future diameter.
* @param timeLeft The time available for the players to go inside the future border (minutes).
* @param sender The user who requested this change.
*/
public void setWarningSize(int diameter, int timeLeft, CommandSender sender)
{
cancelWarning();
this.warningSize = diameter;
if (timeLeft != 0)
{
UHTimer timer = new UHTimer(this.warningTimerName);
timer.setDuration(timeLeft * 60);
p.getTimerManager().registerTimer(timer);
timer.start();
}
if (sender != null)
{
this.warningSender = sender;
}
warningTask = new BorderWarningTask(p);
warningTask.runTaskTimer(p, 20L, 20L * p.getConfig().getInt("map.border.warningInterval", 90));
}
/**
* Sets the size of the future border, used in the warning messages sent to the
* players out of this future border.
*
* This also starts the display of the warning messages, every 90 seconds by default
* (configurable, see config.yml, map.border.warningInterval).
*
* @param diameter The diameter of the future border.
*/
public void setWarningSize(int diameter)
{
setWarningSize(diameter, 0, null);
}
/**
* Returns the UHTimer object representing the countdown before the next border reduction.
*
* <p>Returns {@code null} if there isn't any countdown running currently.</p>
*
* @return The timer.
*/
public UHTimer getWarningTimer()
{
return p.getTimerManager().getTimer(this.warningTimerName);
}
/**
* Stops the display of the warning messages.
*/
public void cancelWarning()
{
if (warningTask != null)
{
try
{
warningTask.cancel();
}
catch (IllegalStateException ignored) {}
}
UHTimer timer = getWarningTimer();
if (timer != null)
{
timer.stop();
p.getTimerManager().unregisterTimer(timer);
}
}
/**
* @return the current border diameter.
*/
public int getCurrentBorderDiameter()
{
return (int) border.getDiameter();
}
/**
* Changes the current border diameter.
* This also reconfigures the used world border.
*
* If WorldBorder is installed, all players out of this new border will be teleported inside the new one.
* Else, nothing will happens.
*
* @param diameter the new diameter.
*/
public void setCurrentBorderDiameter(int diameter)
{
cancelWarning();
border.setDiameter(diameter);
}
/**
* Sends a list of the players outside the given border to the specified sender.
*
* @param to The player/console to send the check.
* @param diameter The diameter of the border to be checked.
*/
public void sendCheckMessage(CommandSender to, int diameter)
{
Set<Player> playersOutside = getPlayersOutside(diameter);
if (playersOutside.size() == 0)
{
to.sendMessage(I.t("{cs}All players are inside the given border."));
}
else
{
to.sendMessage(I.t("{ci}There are {0} players outside the given border.", String.valueOf(playersOutside.size())));
for (Player player : getPlayersOutside(diameter))
{
double distance = getDistanceToBorder(player.getLocation(), diameter);
if (distance > 150)
{
to.sendMessage(I.t("{lightpurple} - {red}{0}{ci} (far away from the border)", player.getName()));
}
else if (distance > 25)
{
to.sendMessage(I.t("{lightpurple} - {yellow}{0}{ci} (close to the border)", player.getName()));
}
else
{
to.sendMessage(I.t("{lightpurple} - {green}{0}{ci} (very close to the border)", player.getName()));
}
}
}
}
/**
* Generates the walls in the given world, following the current border configuration.
*
* @param world The world were the walls will be built in.
* @throws CannotGenerateWallsException
*/
public void generateWalls(World world) throws CannotGenerateWallsException
{
Integer wallHeight = UHConfig.MAP.WALL.HEIGHT.get();
Material wallBlockAir = UHConfig.MAP.WALL.BLOCK.REPLACE_AIR.get();
Material wallBlockSolid = UHConfig.MAP.WALL.BLOCK.REPLACE_SOLID.get();
if (wallBlockAir == null || !wallBlockAir.isSolid() || wallBlockSolid == null || !wallBlockSolid.isSolid())
{
throw new CannotGenerateWallsException("Cannot generate the walls: invalid blocks set in the config");
}
WallGenerator generator = mapShape.getWallGeneratorInstance(wallBlockAir, wallBlockSolid);
if (generator != null)
generator.build(world, getCurrentBorderDiameter(), wallHeight);
else
throw new CannotGenerateWallsException("Unable to load walls generator.");
}
/**
* Schedules the automatic border reduction, if enabled in the configuration.
*/
public void scheduleBorderReduction()
{
if (BORDER_SHRINKING)
{
RunTask.later(new Runnable() {
@Override
public void run()
{
Integer secondsPerBlock = (int) Math.rint(BORDER_SHRINKING_DURATION / (border.getDiameter() - BORDER_SHRINKING_FINAL_SIZE)) * 2;
border.setDiameter(BORDER_SHRINKING_FINAL_SIZE, BORDER_SHRINKING_DURATION);
Titles.broadcastTitle(5, 30, 8, I.t("{red}Warning!"), I.t("{white}The border begins to shrink..."));
Bukkit.broadcastMessage(I.t("{red}{bold}The border begins to shrink..."));
Bukkit.broadcastMessage(I.t("{gray}It will shrink by one block every {0} second(s) until {1} blocks in diameter.", secondsPerBlock, BORDER_SHRINKING_FINAL_SIZE));
}
}, BORDER_SHRINKING_STARTS_AFTER * 20l);
}
}
}